Compare commits
276 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 156e4209f9 | |||
| 7492889f4e | |||
| 0c041120f6 | |||
| ecbb53aaea | |||
| ffe739b7eb | |||
| bd05be6fb6 | |||
| ff7df3818f | |||
| 39899e4edc | |||
| dc578f98e7 | |||
| d7ce41e724 | |||
| eaaeb2c843 | |||
| 0ae7c1efbf | |||
| cb13599739 | |||
| a9251de1dd | |||
| 56798e716a | |||
| 904f0a1197 | |||
| 4d3f2740ce | |||
| f9e7a29c08 | |||
| 6cb14a1c50 | |||
| ea9f933f9e | |||
| eb2e5e3da5 | |||
| 9a4e8bdb48 | |||
| fad0091d87 | |||
| b861151f63 | |||
| feeafc407a | |||
| ea1c1eb803 | |||
| b83e20b571 | |||
| 160ad64e67 | |||
| 1213ff26b4 | |||
| 3bf93f1f85 | |||
| a502581640 | |||
| 1ec041a48f | |||
| fdaf8492ef | |||
| 2433a7e89c | |||
| 6e358d3eab | |||
| 7e462e800f | |||
| 156e54c77f | |||
| 9752ec2655 | |||
| edc1e5fa84 | |||
| a959af087b | |||
| c5994f954a | |||
| 03a09debfa | |||
| 2cf6a9b691 | |||
| 5b712e8dbc | |||
| 5d7f2c706c | |||
| 22f51e1a28 | |||
| 61684ea73d | |||
| 45b37dcd10 | |||
| c0b2230b0b | |||
| 53fa8205f2 | |||
| ddbcf8bb19 | |||
| 6e50ddf5c8 | |||
| dda49f1501 | |||
| 4e2e5aa975 | |||
| 10fd3652d4 | |||
| 4d30510706 | |||
| ee74c4d870 | |||
| 45f5e0cb04 | |||
| 7d983f2847 | |||
| 3b2b5417aa | |||
| 0a0c4eac88 | |||
| d20cd43a99 | |||
| 7b4321ecee | |||
| 188bfd1a7e | |||
| 7b6825d85c | |||
| a27cb9c34e | |||
| 8445ebf107 | |||
| 6df315017c | |||
| 1931e78367 | |||
| 90692d89c5 | |||
| 4f02864e12 | |||
| 7562ec9e1a | |||
| 6d984c0407 | |||
| 3556e591b0 | |||
| 8fdb473d78 | |||
| 398cd15dcf | |||
| 5006b3e837 | |||
| 7216635d84 | |||
| 98ef7c39b7 | |||
| e039a5a9af | |||
| 412a185fa1 | |||
| 20bfe83849 | |||
| 39093130a3 | |||
| 9e9270a8b7 | |||
| 2c904c56f4 | |||
| a3b961e72f | |||
| 0e4c8df418 | |||
| 3b2d595a06 | |||
| b29223c5b6 | |||
| d805ec15b4 | |||
| d5cfdfb50d | |||
| 2e5d73a7e4 | |||
| 1c352cc81b | |||
| 9c6c18780f | |||
| cb23b65797 | |||
| 7a16814808 | |||
| 23553c9944 | |||
| 54fbe8a7c0 | |||
| ea01102f1d | |||
| 15e1e1dfab | |||
| 37ed9cd25b | |||
| 882af04027 | |||
| aa8a298ec7 | |||
| 05ab8fd868 | |||
| 1a736aadf5 | |||
| 2cea653249 | |||
| 1085238031 | |||
| 1356d91423 | |||
| fd2dc14ede | |||
| f91c5d3647 | |||
| 1f3aebf584 | |||
| b39d79a0f9 | |||
| c410914208 | |||
| a046f1caec | |||
| c25f918cc5 | |||
| 6fb1c8d3b9 | |||
| 6047a27c89 | |||
| 8446d016e4 | |||
| ab040d36d5 | |||
| a2781b5bd3 | |||
| a7903f31ac | |||
| c134837ca6 | |||
| f0a57e6714 | |||
| d9b0365c9f | |||
| 948f9456f5 | |||
| 32f94b463f | |||
| 035506496e | |||
| 477222a395 | |||
| 4bdfdfcb36 | |||
| 3167cd0817 | |||
| b52f35259b | |||
| 20bf85b14d | |||
| f02b99a1d0 | |||
| 9132ef57f1 | |||
| d42bf2d43c | |||
| 89042fbf4a | |||
| fc4dcd2db5 | |||
| 4e07d80ebc | |||
| c4a462d601 | |||
| 7fe46fb6f3 | |||
| 2cb94856fd | |||
| f53fc03c6c | |||
| 9278c51035 | |||
| ca9dc5f944 | |||
| f30cfb6166 | |||
| 9614929f77 | |||
| 8e418d4414 | |||
| 5e81bd833b | |||
| cc2ae80e7b | |||
| b921f6097d | |||
| 9679ef893b | |||
| e4fc3cebfd | |||
| 75135819cf | |||
| 072b6cce36 | |||
| 5d60f2cdf2 | |||
| c476593925 | |||
| 089467a419 | |||
| ee68e04f84 | |||
| 9cd46e74be | |||
| 5781220415 | |||
| 965fd66e0f | |||
| 7d3caa2875 | |||
| 418546a659 | |||
| d586c84b56 | |||
| 7b9e5fe99f | |||
| 648f0edc79 | |||
| 4d9d0884c3 | |||
| 19c0bbb94c | |||
| c6995c2283 | |||
| 49a263454c | |||
| 454519220f | |||
| 118fa98ca9 | |||
| 001fa639be | |||
| 009749cf8b | |||
| da94e7b1be | |||
| ea346145f6 | |||
| a01c379c95 | |||
| c9b781d5e1 | |||
| 0b49abf3f5 | |||
| e5fe4b0a99 | |||
| 7474d305fb | |||
| 4716929158 | |||
| 528ca09e8e | |||
| 233054219f | |||
| 0e2c4d4af1 | |||
| f101e9a775 | |||
| bf3863d1bf | |||
| 94e9291c40 | |||
| 459d133b5d | |||
| 773fad66bb | |||
| e250c73109 | |||
| 6870c05ffa | |||
| 199581bf74 | |||
| a9ae971602 | |||
| 913a5b5d0f | |||
| c594137c19 | |||
| fe03c85b97 | |||
| c338652045 | |||
| 1f5cdeb01b | |||
| e53a72c5f5 | |||
| fc2690888e | |||
| b4472fd7d4 | |||
| 796d02506a | |||
| 467f729f06 | |||
| 050ec8b988 | |||
| b2f41e95bf | |||
| e733c91783 | |||
| 4e982722a5 | |||
| 2b1f815c58 | |||
| 0fff1a6754 | |||
| d95d268ec5 | |||
| b4930bc40c | |||
| 5f302238ad | |||
| 7cba2c3f81 | |||
| 218c39b1ec | |||
| e915f4fcd7 | |||
| bc9164b952 | |||
| 7c34be267f | |||
| 042464438c | |||
| cf68e4722a | |||
| 7be37ff76e | |||
| 1118236075 | |||
| ef8a685621 | |||
| e4fef402c9 | |||
| 5528afa404 | |||
| e3189fae37 | |||
| 6d963b378c | |||
| 895ddfa38f | |||
| 28e334a0ba | |||
| d060f5b877 | |||
| 7b70d617e0 | |||
| 261ba4645d | |||
| 2ab7524e71 | |||
| d55969bc65 | |||
| 9976894091 | |||
| 76a0608a04 | |||
| 0d93d335a1 | |||
| ffb9788047 | |||
| 5dd82eede9 | |||
| 14b90466ef | |||
| 43592c3e49 | |||
| b46093b3cc | |||
| 2b9c092705 | |||
| bc73010d4e | |||
| 2d8d416483 | |||
| f549a0691e | |||
| 96c2fb6f54 | |||
| f6d475292c | |||
| bd4d4f49ff | |||
| 5a24eac375 | |||
| a684118dbb | |||
| a324376e60 | |||
| 04e50afaba | |||
| 69494c9212 | |||
| b2f0f02541 | |||
| 71f249113d | |||
| 1d84c00161 | |||
| 5bc7e19a28 | |||
| c46703a05d | |||
| 129a7c39af | |||
| ac3f3e8385 | |||
| bc8ad4df86 | |||
| 53ac3ec582 | |||
| d2d43711c2 | |||
| 510035b7b7 | |||
| c923d19bcc | |||
| bff9597360 | |||
| 78b39a60e8 | |||
| 932966b6b8 | |||
| 85a18e6d75 | |||
| 5d86bf9788 | |||
| 406d9878d8 | |||
| 4e6c5cb27a | |||
| a9c0185bf5 | |||
| 0111172a03 | |||
| 57541488d3 |
@@ -0,0 +1,14 @@
|
||||
coverage:
|
||||
precision: 2
|
||||
round: down
|
||||
range: "50...100"
|
||||
|
||||
status:
|
||||
project:
|
||||
default: on
|
||||
patch:
|
||||
default: on
|
||||
changes:
|
||||
default: off
|
||||
|
||||
comment: false
|
||||
+5
-2
@@ -10,13 +10,17 @@ out/
|
||||
*.ipr
|
||||
*.iws
|
||||
|
||||
**/.DS_Store
|
||||
|
||||
bin/
|
||||
target/
|
||||
build/
|
||||
classes/
|
||||
idea/
|
||||
.gradle/
|
||||
gradle.properties
|
||||
node_modules/
|
||||
|
||||
jadx-output/
|
||||
*-tmp/
|
||||
|
||||
*.dex
|
||||
@@ -24,4 +28,3 @@ gradle.properties
|
||||
*.dump
|
||||
*.log
|
||||
*.cfg
|
||||
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
image: java:8
|
||||
|
||||
variables:
|
||||
GRADLE_OPTS: "-Dorg.gradle.daemon=false"
|
||||
TERM: "dumb"
|
||||
|
||||
before_script:
|
||||
- chmod +x gradlew
|
||||
|
||||
stages:
|
||||
- build
|
||||
|
||||
build:
|
||||
stage: build
|
||||
script:
|
||||
- sed -i " 1 s/.*/&-glb$(git rev-list --count HEAD)-$(git rev-parse --short HEAD)/" version
|
||||
- cat version
|
||||
- ./gradlew -g /cache/.gradle clean build jacocoTestReport
|
||||
- ./gradlew -g /cache/.gradle clean sonarqube -Dsonar.host.url=$SONAR_HOST -Dsonar.organization=$SONAR_ORG -Dsonar.login=$SONAR_TOKEN
|
||||
- ./gradlew -g /cache/.gradle clean dist
|
||||
artifacts:
|
||||
paths:
|
||||
- build/jadx*.zip
|
||||
- build/jadx*.exe
|
||||
@@ -0,0 +1,3 @@
|
||||
[submodule "jadx-test-app/test-app"]
|
||||
path = jadx-test-app/test-app
|
||||
url = git://github.com/skylot/jadx-test-app.git
|
||||
@@ -0,0 +1,16 @@
|
||||
branch: release
|
||||
verifyConditions:
|
||||
- '@semantic-release/github'
|
||||
prepare:
|
||||
- path: '@semantic-release/exec'
|
||||
cmd: "JADX_VERSION=${nextRelease.version} ./gradlew clean dist"
|
||||
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'
|
||||
label: 'zip bundle'
|
||||
- path: 'build/*.exe'
|
||||
label: 'jadx-gui windows'
|
||||
|
||||
+41
-15
@@ -1,24 +1,50 @@
|
||||
language: java
|
||||
jdk:
|
||||
- oraclejdk8
|
||||
- oraclejdk7
|
||||
- openjdk6
|
||||
sudo: false
|
||||
dist: trusty
|
||||
|
||||
# don't build on tag push
|
||||
if: tag IS blank
|
||||
|
||||
git:
|
||||
depth: false
|
||||
|
||||
before_install:
|
||||
- chmod +x gradlew
|
||||
- wget https://github.com/sormuras/bach/raw/master/install-jdk.sh
|
||||
- chmod +x gradlew
|
||||
|
||||
env:
|
||||
global:
|
||||
- TERM=dumb
|
||||
- JADX_VERSION="$(git describe --abbrev=0 --tags)-b$TRAVIS_BUILD_NUMBER-$(git rev-parse --short HEAD)"
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- env: JDK=oracle-8
|
||||
jdk: oraclejdk8
|
||||
- env: JDK=oracle-10
|
||||
install: . ./install-jdk.sh -F 10 -L BCL
|
||||
|
||||
script:
|
||||
- TERM=dumb ./gradlew clean build dist
|
||||
- java -version
|
||||
- ./gradlew clean build
|
||||
|
||||
after_success:
|
||||
- TERM=dumb ./gradlew jacocoTestReport coveralls
|
||||
deploy:
|
||||
- provider: script
|
||||
skip_cleanup: true
|
||||
on:
|
||||
branch: master
|
||||
tags: false
|
||||
condition: $JDK = oracle-8
|
||||
script: bash scripts/travis-master.sh
|
||||
|
||||
sudo: false
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.gradle
|
||||
- provider: script
|
||||
skip_cleanup: true
|
||||
on:
|
||||
branch: release
|
||||
tags: false
|
||||
condition: $JDK = oracle-8
|
||||
script: bash scripts/travis-release.sh
|
||||
|
||||
notifications:
|
||||
email:
|
||||
- skylot@gmail.com
|
||||
email:
|
||||
- skylot@gmail.com
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
# Contribution
|
||||
|
||||
To support this project you can:
|
||||
- Post thoughts about new features/optimizations that important to you
|
||||
- Submit bug using one of following patterns:
|
||||
* Java code examples which decompiles incorrectly
|
||||
* Error log and link to _public available_ apk file or app page on Google play
|
||||
@@ -144,7 +144,8 @@ THE POSSIBILITY OF SUCH DAMAGE.
|
||||
Jadx-gui components
|
||||
===================
|
||||
|
||||
RSyntaxTextArea library licensed under modified BSD license:
|
||||
RSyntaxTextArea library (https://github.com/bobbylight/RSyntaxTextArea)
|
||||
licensed under modified BSD license:
|
||||
|
||||
*******************************************************************************
|
||||
Copyright (c) 2012, Robert Futrell
|
||||
@@ -174,8 +175,39 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*******************************************************************************
|
||||
|
||||
|
||||
Icons copied from several places:
|
||||
- Eclipse Project (JDT UI) - licensed under EPL v1.0 (http://www.eclipse.org/legal/epl-v10.html)
|
||||
- famfamfam silk icon set (http://www.famfamfam.com/lab/icons/silk/) - licensed under Creative Commons Attribution 2.5 License (http://creativecommons.org/licenses/by/2.5/)
|
||||
Concurrent Trees (https://code.google.com/p/concurrent-trees/)
|
||||
licenced under Apache License 2.0:
|
||||
|
||||
*******************************************************************************
|
||||
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
|
||||
|
||||
http://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.
|
||||
*******************************************************************************
|
||||
|
||||
|
||||
Image Viewer (https://github.com/kazocsaba/imageviewer)
|
||||
|
||||
*******************************************************************************
|
||||
Copyright (c) 2008-2012 Kazó Csaba
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*******************************************************************************
|
||||
|
||||
JFontChooser Component - http://sourceforge.jp/projects/jfontchooser/
|
||||
|
||||
Icons copied from several places:
|
||||
- Eclipse Project (JDT UI) - licensed under EPL v1.0 (http://www.eclipse.org/legal/epl-v10.html)
|
||||
- famfamfam silk icon set (http://www.famfamfam.com/lab/icons/silk/) - licensed
|
||||
under Creative Commons Attribution 2.5 License (http://creativecommons.org/licenses/by/2.5/)
|
||||
|
||||
@@ -1,24 +1,33 @@
|
||||
## JADX
|
||||
|
||||
[](https://travis-ci.org/skylot/jadx)
|
||||
[](https://drone.io/github.com/skylot/jadx/latest)
|
||||
[](https://coveralls.io/r/skylot/jadx)
|
||||
[](https://scan.coverity.com/projects/2166)
|
||||
[](https://codecov.io/gh/skylot/jadx)
|
||||
[](https://sonarcloud.io/dashboard?id=jadx)
|
||||
[](http://www.apache.org/licenses/LICENSE-2.0.html)
|
||||
[](https://github.com/semantic-release/semantic-release)
|
||||
|
||||
**jadx** - Dex to Java decompiler
|
||||
|
||||
Command line and GUI tools for produce Java source code from Android Dex and Apk files
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
### Downloads
|
||||
- [unstable](https://drone.io/github.com/skylot/jadx/files)
|
||||
- from [github](https://github.com/skylot/jadx/releases)
|
||||
- from [sourceforge](http://sourceforge.net/projects/jadx/files/)
|
||||
- latest [unstable build:  ](https://bintray.com/skylot/jadx/unstable/_latestVersion#files)
|
||||
- release from [github: ](https://github.com/skylot/jadx/releases/latest)
|
||||
- release from [bintray:  ](https://bintray.com/skylot/jadx/releases/_latestVersion#files)
|
||||
|
||||
After download unpack zip file go to `bin` directory and run:
|
||||
- `jadx` - command line version
|
||||
- `jadx-gui` - graphical version
|
||||
|
||||
On Windows run `.bat` files with double-click\
|
||||
**Note:** ensure you have installed Java 8 64-bit version
|
||||
|
||||
### Building from source
|
||||
Java 8 JDK or higher must be installed:
|
||||
|
||||
git clone https://github.com/skylot/jadx.git
|
||||
cd jadx
|
||||
./gradlew dist
|
||||
@@ -34,7 +43,7 @@ Run **jadx** on itself:
|
||||
|
||||
cd build/jadx/
|
||||
bin/jadx -d out lib/jadx-core-*.jar
|
||||
#or
|
||||
# or
|
||||
bin/jadx-gui lib/jadx-core-*.jar
|
||||
|
||||
|
||||
@@ -42,23 +51,31 @@ Run **jadx** on itself:
|
||||
```
|
||||
jadx[-gui] [options] <input file> (.dex, .apk, .jar or .class)
|
||||
options:
|
||||
-d, --output-dir - output directory
|
||||
-j, --threads-count - processing threads count
|
||||
-f, --fallback - make simple dump (using goto instead of 'if', 'for', etc)
|
||||
-r, --no-res - do not decode resources
|
||||
-s, --no-src - do not decompile source code
|
||||
--show-bad-code - show inconsistent code (incorrectly decompiled)
|
||||
--cfg - save methods control flow graph to dot file
|
||||
--raw-cfg - save methods control flow graph (use raw instructions)
|
||||
-v, --verbose - verbose output
|
||||
--deobf - activate deobfuscation
|
||||
--deobf-min - min length of name
|
||||
--deobf-max - max length of name
|
||||
--deobf-rewrite-cfg - force to save deobfuscation map
|
||||
-h, --help - print this help
|
||||
-d, --output-dir - output directory
|
||||
-ds, --output-dir-src - output directory for sources
|
||||
-dr, --output-dir-res - output directory for resources
|
||||
-j, --threads-count - processing threads count
|
||||
-r, --no-res - do not decode resources
|
||||
-s, --no-src - do not decompile source code
|
||||
-e, --export-gradle - save as android gradle project
|
||||
--show-bad-code - show inconsistent code (incorrectly decompiled)
|
||||
--no-imports - disable use of imports, always write entire package name
|
||||
--no-replace-consts - don't replace constant value with matching constant field
|
||||
--escape-unicode - escape non latin characters in strings (with \u)
|
||||
--deobf - activate deobfuscation
|
||||
--deobf-min - min length of name
|
||||
--deobf-max - max length of name
|
||||
--deobf-rewrite-cfg - force to save deobfuscation map
|
||||
--deobf-use-sourcename - use source file name as class name alias
|
||||
--cfg - save methods control flow graph to dot file
|
||||
--raw-cfg - save methods control flow graph (use raw instructions)
|
||||
-f, --fallback - make simple dump (using goto instead of 'if', 'for', etc)
|
||||
-v, --verbose - verbose output
|
||||
-h, --help - print this help
|
||||
Example:
|
||||
jadx -d out classes.dex
|
||||
```
|
||||
These options also worked on jadx-gui running from command line and override options from preferences dialog
|
||||
|
||||
### Troubleshooting
|
||||
##### Out of memory error:
|
||||
@@ -69,21 +86,7 @@ Example:
|
||||
* edit 'jadx' script (jadx.bat on Windows) and setup bigger heap size:
|
||||
`DEFAULT_JVM_OPTS="-Xmx2500M"`
|
||||
|
||||
|
||||
### Contribution
|
||||
|
||||
To support this project you can:
|
||||
- Post thoughts about new features/optimizations that important to you
|
||||
- Submit bug using one of following patterns:
|
||||
* Java code examples which decompiles incorrectly
|
||||
* Error log and link to _public available_ apk file or app page on Google play
|
||||
|
||||
And any other comments will be very helpfull,
|
||||
because at current stage of development it is very time consuming
|
||||
to **find** new bugs, design and implement new features.
|
||||
Also I need to **prioritize** these task for complete most important at first.
|
||||
|
||||
---------------------------------------
|
||||
*Licensed under the Apache 2.0 License*
|
||||
|
||||
*Copyright 2015 by Skylot*
|
||||
*Copyright 2018 by Skylot*
|
||||
|
||||
+54
-34
@@ -1,38 +1,34 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
jcenter()
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id "com.github.kt3k.coveralls" version "2.3.1"
|
||||
id "info.solidsoft.pitest" version "1.1.4"
|
||||
// id "com.github.ben-manes.versions" version "0.8"
|
||||
id 'com.github.ksoichiro.console.reporter' version '0.5.0'
|
||||
id 'org.sonarqube' version '2.6.2'
|
||||
id 'com.github.ben-manes.versions' version '0.20.0'
|
||||
}
|
||||
|
||||
apply plugin: 'sonar-runner'
|
||||
|
||||
ext.jadxVersion = file('version').readLines().get(0)
|
||||
ext.jadxVersion = System.getenv('JADX_VERSION') ?: "dev"
|
||||
version = jadxVersion
|
||||
println("jadx version: ${jadxVersion}")
|
||||
|
||||
subprojects {
|
||||
allprojects {
|
||||
apply plugin: 'java'
|
||||
apply plugin: 'groovy'
|
||||
apply plugin: 'jacoco'
|
||||
apply plugin: 'com.github.kt3k.coveralls'
|
||||
apply plugin: 'com.github.ksoichiro.console.reporter'
|
||||
|
||||
version = jadxVersion
|
||||
|
||||
tasks.withType(JavaCompile) {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_6
|
||||
targetCompatibility = JavaVersion.VERSION_1_6
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
|
||||
if (!"$it".contains(':jadx-samples:')) {
|
||||
options.compilerArgs << '-Xlint' << '-Xlint:unchecked' << '-Xlint:deprecation'
|
||||
}
|
||||
}
|
||||
|
||||
compileJava {
|
||||
options.encoding = "UTF-8"
|
||||
}
|
||||
|
||||
jar {
|
||||
version = jadxVersion
|
||||
manifest {
|
||||
@@ -41,36 +37,51 @@ subprojects {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile 'org.slf4j:slf4j-api:1.7.10'
|
||||
compile 'org.slf4j:slf4j-api:1.7.25'
|
||||
|
||||
testCompile 'ch.qos.logback:logback-classic:1.1.2'
|
||||
testCompile 'ch.qos.logback:logback-classic:1.2.3'
|
||||
testCompile 'junit:junit:4.12'
|
||||
testCompile 'org.hamcrest:hamcrest-library:1.3'
|
||||
testCompile 'org.mockito:mockito-core:1.10.19'
|
||||
testCompile 'org.spockframework:spock-core:1.0-groovy-2.4'
|
||||
testCompile 'cglib:cglib-nodep:3.1'
|
||||
testCompile 'org.mockito:mockito-core:2.20.1'
|
||||
testCompile 'org.spockframework:spock-core:1.1-groovy-2.4'
|
||||
testCompile 'cglib:cglib-nodep:3.2.7'
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
jcenter()
|
||||
}
|
||||
|
||||
jacoco {
|
||||
toolVersion = "0.8.1"
|
||||
}
|
||||
jacocoTestReport {
|
||||
reports {
|
||||
xml.enabled = true // coveralls plugin depends on xml format report
|
||||
xml.enabled = true
|
||||
html.enabled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Sonar runner configuration */
|
||||
repositories {
|
||||
mavenCentral()
|
||||
sonarqube {
|
||||
properties {
|
||||
property 'sonar.exclusions', '**/jadx/samples/**/*,**/test-app/**/*'
|
||||
property 'sonar.coverage.exclusions', '**/jadx/gui/**/*'
|
||||
}
|
||||
}
|
||||
sonarRunner {
|
||||
toolVersion = '2.4'
|
||||
|
||||
dependencyUpdates.resolutionStrategy = {
|
||||
componentSelection { rules ->
|
||||
rules.all { ComponentSelection selection ->
|
||||
boolean rejected = ['alpha', 'beta', 'rc', 'cr', 'm', 'atlassian'].any { qualifier ->
|
||||
selection.candidate.version ==~ /(?i).*[.-]${qualifier}[.\d-]*/
|
||||
}
|
||||
if (rejected) {
|
||||
selection.reject('Release candidate')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
task copyArtifacts(type: Sync, dependsOn: ['jadx-cli:installDist', 'jadx-gui:installDist']) {
|
||||
@@ -86,17 +97,29 @@ task pack(type: Zip, dependsOn: copyArtifacts) {
|
||||
from copyArtifacts.destinationDir
|
||||
}
|
||||
|
||||
task dist(dependsOn: pack) {
|
||||
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 pitest(overwrite: true, dependsOn: 'jadx-core:pitest') {
|
||||
task testAppCheck(dependsOn: 'jadx-test-app:testAppCheck') {
|
||||
group 'jadx'
|
||||
}
|
||||
|
||||
task cleanBuildDir(type: Delete) {
|
||||
group 'jadx'
|
||||
delete buildDir
|
||||
}
|
||||
|
||||
@@ -104,6 +127,3 @@ build.dependsOn(dist, samples)
|
||||
|
||||
clean.dependsOn(cleanBuildDir)
|
||||
|
||||
task wrapper(type: Wrapper) {
|
||||
gradleVersion = '2.3'
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
org.gradle.daemon=false
|
||||
Vendored
BIN
Binary file not shown.
+1
-1
@@ -1,5 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-2.3-all.zip
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
#!/usr/bin/env sh
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
@@ -6,47 +6,6 @@
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS=""
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn ( ) {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die ( ) {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
esac
|
||||
|
||||
# For Cygwin, ensure paths are in UNIX format before anything is touched.
|
||||
if $cygwin ; then
|
||||
[ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
|
||||
fi
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
@@ -61,9 +20,49 @@ while [ -h "$PRG" ] ; do
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >&-
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >&-
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS=""
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
@@ -90,7 +89,7 @@ location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
@@ -114,6 +113,7 @@ fi
|
||||
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`
|
||||
@@ -154,11 +154,19 @@ if $cygwin ; then
|
||||
esac
|
||||
fi
|
||||
|
||||
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
|
||||
function splitJvmOpts() {
|
||||
JVM_OPTS=("$@")
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
|
||||
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
|
||||
APP_ARGS=$(save "$@")
|
||||
|
||||
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
|
||||
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
||||
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
||||
cd "$(dirname "$0")"
|
||||
fi
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
|
||||
Vendored
+4
-10
@@ -8,14 +8,14 @@
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
@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 DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@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=
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
@@ -46,10 +46,9 @@ echo location of your Java installation.
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windowz variants
|
||||
@rem Get command-line arguments, handling Windows variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
if "%@eval[2+2]" == "4" goto 4NT_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
@@ -60,11 +59,6 @@ set _SKIP=2
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
goto execute
|
||||
|
||||
:4NT_args
|
||||
@rem Get arguments from the 4NT Shell from JP Software
|
||||
set CMD_LINE_ARGS=%$
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
@@ -5,8 +5,8 @@ applicationName = 'jadx'
|
||||
|
||||
dependencies {
|
||||
compile(project(':jadx-core'))
|
||||
compile 'com.beust:jcommander:1.47'
|
||||
compile 'ch.qos.logback:logback-classic:1.1.2'
|
||||
compile 'com.beust:jcommander:1.74'
|
||||
compile 'ch.qos.logback:logback-classic:1.2.3'
|
||||
}
|
||||
|
||||
applicationDistribution.with {
|
||||
@@ -18,3 +18,6 @@ applicationDistribution.with {
|
||||
}
|
||||
}
|
||||
|
||||
startScripts {
|
||||
defaultJvmOpts = ['-Xms128M', '-Xmx4g']
|
||||
}
|
||||
|
||||
@@ -1,68 +1,45 @@
|
||||
package jadx.cli;
|
||||
|
||||
import jadx.api.JadxDecompiler;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.JadxDecompiler;
|
||||
import jadx.core.utils.exceptions.JadxArgsValidateException;
|
||||
|
||||
public class JadxCLI {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(JadxCLI.class);
|
||||
|
||||
public static void main(String[] args) throws JadxException {
|
||||
public static void main(String[] args) {
|
||||
int result = 0;
|
||||
try {
|
||||
JadxCLIArgs jadxArgs = new JadxCLIArgs();
|
||||
if (processArgs(jadxArgs, args)) {
|
||||
processAndSave(jadxArgs);
|
||||
if (jadxArgs.processArgs(args)) {
|
||||
result = processAndSave(jadxArgs);
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
} catch (Exception e) {
|
||||
LOG.error("jadx error: {}", e.getMessage(), e);
|
||||
System.exit(1);
|
||||
result = 1;
|
||||
} finally {
|
||||
System.exit(result);
|
||||
}
|
||||
}
|
||||
|
||||
static void processAndSave(JadxCLIArgs jadxArgs) throws JadxException {
|
||||
JadxDecompiler jadx = new JadxDecompiler(jadxArgs);
|
||||
jadx.setOutputDir(jadxArgs.getOutDir());
|
||||
jadx.loadFiles(jadxArgs.getInput());
|
||||
static int processAndSave(JadxCLIArgs inputArgs) {
|
||||
JadxDecompiler jadx = new JadxDecompiler(inputArgs.toJadxArgs());
|
||||
try {
|
||||
jadx.load();
|
||||
} catch (JadxArgsValidateException e) {
|
||||
LOG.error("Incorrect arguments: {}", e.getMessage());
|
||||
return 1;
|
||||
}
|
||||
jadx.save();
|
||||
if (jadx.getErrorsCount() != 0) {
|
||||
int errorsCount = jadx.getErrorsCount();
|
||||
if (errorsCount != 0) {
|
||||
jadx.printErrorsReport();
|
||||
LOG.error("finished with errors");
|
||||
} else {
|
||||
LOG.info("done");
|
||||
}
|
||||
}
|
||||
|
||||
static boolean processArgs(JadxCLIArgs jadxArgs, String[] args) throws JadxException {
|
||||
if (!jadxArgs.processArgs(args)) {
|
||||
return false;
|
||||
}
|
||||
if (jadxArgs.getInput().isEmpty()) {
|
||||
LOG.error("Please specify input file");
|
||||
jadxArgs.printUsage();
|
||||
return false;
|
||||
}
|
||||
File outputDir = jadxArgs.getOutDir();
|
||||
if (outputDir == null) {
|
||||
String outDirName;
|
||||
File file = jadxArgs.getInput().get(0);
|
||||
String name = file.getName();
|
||||
int pos = name.lastIndexOf('.');
|
||||
if (pos != -1) {
|
||||
outDirName = name.substring(0, pos);
|
||||
} else {
|
||||
outDirName = name + "-jadx-out";
|
||||
}
|
||||
LOG.info("output directory: {}", outDirName);
|
||||
outputDir = new File(outDirName);
|
||||
jadxArgs.setOutputDir(outputDir);
|
||||
}
|
||||
if (outputDir.exists() && !outputDir.isDirectory()) {
|
||||
throw new JadxException("Output directory exists as file " + outputDir);
|
||||
}
|
||||
return true;
|
||||
return errorsCount;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,38 +1,40 @@
|
||||
package jadx.cli;
|
||||
|
||||
import jadx.api.IJadxArgs;
|
||||
import jadx.api.JadxDecompiler;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.PrintStream;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import ch.qos.logback.classic.spi.ILoggingEvent;
|
||||
import ch.qos.logback.core.Appender;
|
||||
import com.beust.jcommander.JCommander;
|
||||
import com.beust.jcommander.Parameter;
|
||||
import com.beust.jcommander.ParameterDescription;
|
||||
import com.beust.jcommander.ParameterException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class JadxCLIArgs implements IJadxArgs {
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.api.JadxDecompiler;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
import jadx.core.utils.files.FileUtils;
|
||||
|
||||
@Parameter(description = "<input file> (.dex, .apk, .jar or .class)")
|
||||
protected List<String> files;
|
||||
public class JadxCLIArgs {
|
||||
|
||||
@Parameter(description = "<input file> (.apk, .dex, .jar or .class)")
|
||||
protected List<String> files = new ArrayList<>(1);
|
||||
|
||||
@Parameter(names = {"-d", "--output-dir"}, description = "output directory")
|
||||
protected String outDirName;
|
||||
protected String outDir;
|
||||
|
||||
@Parameter(names = {"-j", "--threads-count"}, description = "processing threads count")
|
||||
protected int threadsCount = Runtime.getRuntime().availableProcessors();
|
||||
@Parameter(names = {"-ds", "--output-dir-src"}, description = "output directory for sources")
|
||||
protected String outDirSrc;
|
||||
|
||||
@Parameter(names = {"-f", "--fallback"}, description = "make simple dump (using goto instead of 'if', 'for', etc)")
|
||||
protected boolean fallbackMode = false;
|
||||
@Parameter(names = {"-dr", "--output-dir-res"}, description = "output directory for resources")
|
||||
protected String outDirRes;
|
||||
|
||||
@Parameter(names = {"-r", "--no-res"}, description = "do not decode resources")
|
||||
protected boolean skipResources = false;
|
||||
@@ -40,43 +42,64 @@ public class JadxCLIArgs implements IJadxArgs {
|
||||
@Parameter(names = {"-s", "--no-src"}, description = "do not decompile source code")
|
||||
protected boolean skipSources = false;
|
||||
|
||||
@Parameter(names = {"-e", "--export-gradle"}, description = "save as android gradle project")
|
||||
protected boolean exportAsGradleProject = false;
|
||||
|
||||
@Parameter(names = {"-j", "--threads-count"}, description = "processing threads count")
|
||||
protected int threadsCount = JadxArgs.DEFAULT_THREADS_COUNT;
|
||||
|
||||
@Parameter(names = {"--show-bad-code"}, description = "show inconsistent code (incorrectly decompiled)")
|
||||
protected boolean showInconsistentCode = false;
|
||||
|
||||
@Parameter(names = {"--no-imports"}, description = "disable use of imports, always write entire package name")
|
||||
protected boolean useImports = true;
|
||||
|
||||
@Parameter(names = "--no-replace-consts", description = "don't replace constant value with matching constant field")
|
||||
protected boolean replaceConsts = true;
|
||||
|
||||
@Parameter(names = {"--escape-unicode"}, description = "escape non latin characters in strings (with \\u)")
|
||||
protected boolean escapeUnicode = false;
|
||||
|
||||
@Parameter(names = {"--deobf"}, description = "activate deobfuscation")
|
||||
protected boolean deobfuscationOn = false;
|
||||
|
||||
@Parameter(names = {"--deobf-min"}, description = "min length of name, renamed if shorter")
|
||||
protected int deobfuscationMinLength = 3;
|
||||
|
||||
@Parameter(names = {"--deobf-max"}, description = "max length of name, renamed if longer")
|
||||
protected int deobfuscationMaxLength = 64;
|
||||
|
||||
@Parameter(names = {"--deobf-rewrite-cfg"}, description = "force to save deobfuscation map")
|
||||
protected boolean deobfuscationForceSave = false;
|
||||
|
||||
@Parameter(names = {"--deobf-use-sourcename"}, description = "use source file name as class name alias")
|
||||
protected boolean deobfuscationUseSourceNameAsAlias = true;
|
||||
|
||||
@Parameter(names = {"--cfg"}, description = "save methods control flow graph to dot file")
|
||||
protected boolean cfgOutput = false;
|
||||
|
||||
@Parameter(names = {"--raw-cfg"}, description = "save methods control flow graph (use raw instructions)")
|
||||
protected boolean rawCfgOutput = false;
|
||||
|
||||
@Parameter(names = {"-f", "--fallback"}, description = "make simple dump (using goto instead of 'if', 'for', etc)")
|
||||
protected boolean fallbackMode = false;
|
||||
|
||||
@Parameter(names = {"-v", "--verbose"}, description = "verbose output")
|
||||
protected boolean verbose = false;
|
||||
|
||||
@Parameter(names = {"--deobf"}, description = "activate deobfuscation")
|
||||
protected boolean deobfuscationOn = false;
|
||||
|
||||
@Parameter(names = {"--deobf-min"}, description = "min length of name")
|
||||
protected int deobfuscationMinLength = 2;
|
||||
|
||||
@Parameter(names = {"--deobf-max"}, description = "max length of name")
|
||||
protected int deobfuscationMaxLength = 64;
|
||||
|
||||
@Parameter(names = {"--deobf-rewrite-cfg"}, description = "force to save deobfuscation map")
|
||||
protected boolean deobfuscationForceSave = false;
|
||||
@Parameter(names = {"--version"}, description = "print jadx version")
|
||||
protected boolean printVersion = false;
|
||||
|
||||
@Parameter(names = {"-h", "--help"}, description = "print this help", help = true)
|
||||
protected boolean printHelp = false;
|
||||
|
||||
private final List<File> input = new ArrayList<File>(1);
|
||||
private File outputDir;
|
||||
|
||||
public boolean processArgs(String[] args) {
|
||||
return parse(args) && process();
|
||||
}
|
||||
|
||||
private boolean parse(String[] args) {
|
||||
try {
|
||||
new JCommander(this, args);
|
||||
makeJCommander().parse(args);
|
||||
return true;
|
||||
} catch (ParameterException e) {
|
||||
System.err.println("Arguments parse error: " + e.getMessage());
|
||||
@@ -85,35 +108,31 @@ public class JadxCLIArgs implements IJadxArgs {
|
||||
}
|
||||
}
|
||||
|
||||
private JCommander makeJCommander() {
|
||||
return JCommander.newBuilder().addObject(this).build();
|
||||
}
|
||||
|
||||
private boolean process() {
|
||||
if (isPrintHelp()) {
|
||||
if (printHelp) {
|
||||
printUsage();
|
||||
return false;
|
||||
}
|
||||
if (printVersion) {
|
||||
System.out.println(JadxDecompiler.getVersion());
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
if (threadsCount <= 0) {
|
||||
throw new JadxException("Threads count must be positive");
|
||||
throw new JadxException("Threads count must be positive, got: " + threadsCount);
|
||||
}
|
||||
if (files != null) {
|
||||
for (String fileName : files) {
|
||||
File file = new File(fileName);
|
||||
if (file.exists()) {
|
||||
input.add(file);
|
||||
} else {
|
||||
throw new JadxException("File not found: " + file);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (input.size() > 1) {
|
||||
throw new JadxException("Only one input file is supported");
|
||||
}
|
||||
if (outDirName != null) {
|
||||
outputDir = new File(outDirName);
|
||||
}
|
||||
if (isVerbose()) {
|
||||
if (verbose) {
|
||||
ch.qos.logback.classic.Logger rootLogger =
|
||||
(ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
|
||||
rootLogger.setLevel(ch.qos.logback.classic.Level.DEBUG);
|
||||
// remove INFO ThresholdFilter
|
||||
Appender<ILoggingEvent> appender = rootLogger.getAppender("STDOUT");
|
||||
if (appender != null) {
|
||||
appender.clearAllFilters();
|
||||
}
|
||||
}
|
||||
} catch (JadxException e) {
|
||||
System.err.println("ERROR: " + e.getMessage());
|
||||
@@ -124,7 +143,7 @@ public class JadxCLIArgs implements IJadxArgs {
|
||||
}
|
||||
|
||||
public void printUsage() {
|
||||
JCommander jc = new JCommander(this);
|
||||
JCommander jc = makeJCommander();
|
||||
// print usage in not sorted fields order (by default its sorted by description)
|
||||
PrintStream out = System.out;
|
||||
out.println();
|
||||
@@ -134,7 +153,7 @@ public class JadxCLIArgs implements IJadxArgs {
|
||||
out.println("options:");
|
||||
|
||||
List<ParameterDescription> params = jc.getParameters();
|
||||
Map<String, ParameterDescription> paramsMap = new LinkedHashMap<String, ParameterDescription>(params.size());
|
||||
Map<String, ParameterDescription> paramsMap = new LinkedHashMap<>(params.size());
|
||||
int maxNamesLen = 0;
|
||||
for (ParameterDescription p : params) {
|
||||
paramsMap.put(p.getParameterized().getName(), p);
|
||||
@@ -143,7 +162,8 @@ public class JadxCLIArgs implements IJadxArgs {
|
||||
maxNamesLen = len;
|
||||
}
|
||||
}
|
||||
Field[] fields = JadxCLIArgs.class.getDeclaredFields();
|
||||
JadxCLIArgs args = new JadxCLIArgs();
|
||||
Field[] fields = args.getClass().getDeclaredFields();
|
||||
for (Field f : fields) {
|
||||
String name = f.getName();
|
||||
ParameterDescription p = paramsMap.get(name);
|
||||
@@ -151,13 +171,26 @@ public class JadxCLIArgs implements IJadxArgs {
|
||||
continue;
|
||||
}
|
||||
StringBuilder opt = new StringBuilder();
|
||||
opt.append(' ').append(p.getNames());
|
||||
addSpaces(opt, maxNamesLen - opt.length() + 2);
|
||||
opt.append(" ").append(p.getNames());
|
||||
addSpaces(opt, maxNamesLen - opt.length() + 3);
|
||||
opt.append("- ").append(p.getDescription());
|
||||
addDefaultValue(args, f, opt);
|
||||
out.println(opt);
|
||||
}
|
||||
out.println("Example:");
|
||||
out.println(" jadx -d out classes.dex");
|
||||
out.println(" jadx -d out classes.dex");
|
||||
}
|
||||
|
||||
private void addDefaultValue(JadxCLIArgs args, Field f, StringBuilder opt) {
|
||||
Class<?> fieldType = f.getType();
|
||||
if (fieldType == int.class) {
|
||||
try {
|
||||
int val = f.getInt(args);
|
||||
opt.append(" (default: ").append(val).append(")");
|
||||
} catch (Exception e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void addSpaces(StringBuilder str, int count) {
|
||||
@@ -166,80 +199,112 @@ public class JadxCLIArgs implements IJadxArgs {
|
||||
}
|
||||
}
|
||||
|
||||
public List<File> getInput() {
|
||||
return input;
|
||||
public JadxArgs toJadxArgs() {
|
||||
JadxArgs args = new JadxArgs();
|
||||
args.setInputFiles(files.stream().map(FileUtils::toFile).collect(Collectors.toList()));
|
||||
args.setOutDir(FileUtils.toFile(outDir));
|
||||
args.setOutDirSrc(FileUtils.toFile(outDirSrc));
|
||||
args.setOutDirRes(FileUtils.toFile(outDirRes));
|
||||
args.setThreadsCount(threadsCount);
|
||||
args.setSkipSources(skipSources);
|
||||
args.setSkipResources(skipResources);
|
||||
args.setFallbackMode(fallbackMode);
|
||||
args.setShowInconsistentCode(showInconsistentCode);
|
||||
args.setCfgOutput(cfgOutput);
|
||||
args.setRawCFGOutput(rawCfgOutput);
|
||||
args.setReplaceConsts(replaceConsts);
|
||||
args.setDeobfuscationOn(deobfuscationOn);
|
||||
args.setDeobfuscationForceSave(deobfuscationForceSave);
|
||||
args.setDeobfuscationMinLength(deobfuscationMinLength);
|
||||
args.setDeobfuscationMaxLength(deobfuscationMaxLength);
|
||||
args.setUseSourceNameAsClassAlias(deobfuscationUseSourceNameAsAlias);
|
||||
args.setEscapeUnicode(escapeUnicode);
|
||||
args.setExportAsGradleProject(exportAsGradleProject);
|
||||
args.setUseImports(useImports);
|
||||
return args;
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getOutDir() {
|
||||
return outputDir;
|
||||
public List<String> getFiles() {
|
||||
return files;
|
||||
}
|
||||
|
||||
public void setOutputDir(File outputDir) {
|
||||
this.outputDir = outputDir;
|
||||
public String getOutDir() {
|
||||
return outDir;
|
||||
}
|
||||
|
||||
public boolean isPrintHelp() {
|
||||
return printHelp;
|
||||
public String getOutDirSrc() {
|
||||
return outDirSrc;
|
||||
}
|
||||
|
||||
public String getOutDirRes() {
|
||||
return outDirRes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSkipResources() {
|
||||
return skipResources;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSkipSources() {
|
||||
return skipSources;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getThreadsCount() {
|
||||
return threadsCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCFGOutput() {
|
||||
return cfgOutput;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRawCFGOutput() {
|
||||
return rawCfgOutput;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFallbackMode() {
|
||||
return fallbackMode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isShowInconsistentCode() {
|
||||
return showInconsistentCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVerbose() {
|
||||
return verbose;
|
||||
public boolean isUseImports() {
|
||||
return useImports;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDeobfuscationOn() {
|
||||
return deobfuscationOn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDeobfuscationMinLength() {
|
||||
return deobfuscationMinLength;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDeobfuscationMaxLength() {
|
||||
return deobfuscationMaxLength;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDeobfuscationForceSave() {
|
||||
return deobfuscationForceSave;
|
||||
}
|
||||
|
||||
public boolean isDeobfuscationUseSourceNameAsAlias() {
|
||||
return deobfuscationUseSourceNameAsAlias;
|
||||
}
|
||||
|
||||
public boolean escapeUnicode() {
|
||||
return escapeUnicode;
|
||||
}
|
||||
|
||||
public boolean isEscapeUnicode() {
|
||||
return escapeUnicode;
|
||||
}
|
||||
|
||||
public boolean isCfgOutput() {
|
||||
return cfgOutput;
|
||||
}
|
||||
|
||||
public boolean isRawCfgOutput() {
|
||||
return rawCfgOutput;
|
||||
}
|
||||
|
||||
public boolean isReplaceConsts() {
|
||||
return replaceConsts;
|
||||
}
|
||||
|
||||
public boolean isExportAsGradleProject() {
|
||||
return exportAsGradleProject;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
<configuration>
|
||||
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
|
||||
<level>INFO</level>
|
||||
</filter>
|
||||
<encoder>
|
||||
<pattern>%-5level - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="INFO">
|
||||
<root level="DEBUG">
|
||||
<appender-ref ref="STDOUT"/>
|
||||
</root>
|
||||
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package jadx.cli;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
public class JadxCLIArgsTest {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(JadxCLIArgsTest.class);
|
||||
|
||||
@Test
|
||||
public void testInvertedBooleanOption() {
|
||||
assertThat(parse("--no-replace-consts").isReplaceConsts(), is(false));
|
||||
assertThat(parse("").isReplaceConsts(), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEscapeUnicodeOption() {
|
||||
assertThat(parse("--escape-unicode").isEscapeUnicode(), is(true));
|
||||
assertThat(parse("").isEscapeUnicode(), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSrcOption() {
|
||||
assertThat(parse("--no-src").isSkipSources(), is(true));
|
||||
assertThat(parse("-s").isSkipSources(), is(true));
|
||||
assertThat(parse("").isSkipSources(), is(false));
|
||||
}
|
||||
|
||||
private JadxCLIArgs parse(String... args) {
|
||||
JadxCLIArgs jadxArgs = new JadxCLIArgs();
|
||||
boolean res = jadxArgs.processArgs(args);
|
||||
assertThat(res, is(true));
|
||||
LOG.info("Jadx args: {}", jadxArgs.toJadxArgs());
|
||||
return jadxArgs;
|
||||
}
|
||||
}
|
||||
+9
-19
@@ -1,27 +1,17 @@
|
||||
ext.jadxClasspath = 'clsp-data/android-5.1.jar'
|
||||
|
||||
apply plugin: "info.solidsoft.pitest"
|
||||
|
||||
dependencies {
|
||||
runtime files(jadxClasspath)
|
||||
|
||||
compile files('lib/dx-1.10.jar')
|
||||
compile 'commons-io:commons-io:2.4'
|
||||
compile 'org.ow2.asm:asm:5.0.3'
|
||||
compile 'com.intellij:annotations:12.0'
|
||||
compile files('lib/dx-1.14.jar')
|
||||
compile 'commons-io:commons-io:2.6'
|
||||
compile 'org.ow2.asm:asm:6.2'
|
||||
compile 'org.jetbrains:annotations:16.0.2'
|
||||
compile 'uk.com.robust-it:cloning:1.9.10'
|
||||
|
||||
testCompile 'org.smali:smali:2.0.3'
|
||||
testCompile 'org.smali:smali:2.2.4'
|
||||
testCompile 'org.smali:baksmali:2.2.4'
|
||||
|
||||
testCompile 'org.apache.commons:commons-lang3:3.7'
|
||||
}
|
||||
|
||||
task packTests(type: Jar) {
|
||||
classifier = 'tests'
|
||||
from sourceSets.test.output
|
||||
}
|
||||
|
||||
pitest {
|
||||
excludedMethods = ['toString']
|
||||
threads = 4
|
||||
enableDefaultIncrementalAnalysis = true
|
||||
outputFormats = ['XML', 'HTML']
|
||||
jvmArgs = ['-Xmx12G']
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -2,24 +2,32 @@ package jadx.api;
|
||||
|
||||
public final class CodePosition {
|
||||
|
||||
private final JavaClass cls;
|
||||
private final JavaNode node;
|
||||
private final int line;
|
||||
private final int offset;
|
||||
|
||||
public CodePosition(JavaClass cls, int line, int offset) {
|
||||
this.cls = cls;
|
||||
public CodePosition(JavaNode node, int line, int offset) {
|
||||
this.node = node;
|
||||
this.line = line;
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
public CodePosition(int line, int offset) {
|
||||
this.cls = null;
|
||||
this.node = null;
|
||||
this.line = line;
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
public JavaNode getNode() {
|
||||
return node;
|
||||
}
|
||||
|
||||
public JavaClass getJavaClass() {
|
||||
return cls;
|
||||
JavaClass parent = node.getDeclaringClass();
|
||||
if (parent == null && node instanceof JavaClass) {
|
||||
return (JavaClass) node;
|
||||
}
|
||||
return parent;
|
||||
}
|
||||
|
||||
public int getLine() {
|
||||
@@ -30,10 +38,6 @@ public final class CodePosition {
|
||||
return offset;
|
||||
}
|
||||
|
||||
public boolean isSet() {
|
||||
return line != 0 || offset != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
@@ -53,6 +57,6 @@ public final class CodePosition {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return line + ":" + offset + (cls != null ? " " + cls : "");
|
||||
return line + ":" + offset + (node != null ? " " + node : "");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
package jadx.api;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class DefaultJadxArgs implements IJadxArgs {
|
||||
|
||||
@Override
|
||||
public File getOutDir() {
|
||||
return new File("jadx-output");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getThreadsCount() {
|
||||
return Runtime.getRuntime().availableProcessors();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCFGOutput() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRawCFGOutput() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFallbackMode() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isShowInconsistentCode() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVerbose() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSkipResources() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSkipSources() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDeobfuscationOn() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDeobfuscationMinLength() {
|
||||
return Integer.MIN_VALUE + 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDeobfuscationMaxLength() {
|
||||
return Integer.MAX_VALUE - 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDeobfuscationForceSave() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
package jadx.api;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public interface IJadxArgs {
|
||||
File getOutDir();
|
||||
|
||||
int getThreadsCount();
|
||||
|
||||
boolean isCFGOutput();
|
||||
|
||||
boolean isRawCFGOutput();
|
||||
|
||||
boolean isFallbackMode();
|
||||
|
||||
boolean isShowInconsistentCode();
|
||||
|
||||
boolean isVerbose();
|
||||
|
||||
boolean isSkipResources();
|
||||
|
||||
boolean isSkipSources();
|
||||
|
||||
boolean isDeobfuscationOn();
|
||||
|
||||
int getDeobfuscationMinLength();
|
||||
|
||||
int getDeobfuscationMaxLength();
|
||||
|
||||
boolean isDeobfuscationForceSave();
|
||||
}
|
||||
@@ -0,0 +1,241 @@
|
||||
package jadx.api;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class JadxArgs {
|
||||
|
||||
public static final int DEFAULT_THREADS_COUNT = Math.max(1, Runtime.getRuntime().availableProcessors() / 2);
|
||||
|
||||
public static final String DEFAULT_OUT_DIR = "jadx-output";
|
||||
public static final String DEFAULT_SRC_DIR = "sources";
|
||||
public static final String DEFAULT_RES_DIR = "resources";
|
||||
|
||||
private List<File> inputFiles = new ArrayList<>(1);
|
||||
|
||||
private File outDir;
|
||||
private File outDirSrc;
|
||||
private File outDirRes;
|
||||
|
||||
private int threadsCount = DEFAULT_THREADS_COUNT;
|
||||
|
||||
private boolean cfgOutput = false;
|
||||
private boolean rawCFGOutput = false;
|
||||
|
||||
private boolean fallbackMode = false;
|
||||
private boolean showInconsistentCode = false;
|
||||
|
||||
private boolean useImports = true;
|
||||
|
||||
private boolean isSkipResources = false;
|
||||
private boolean isSkipSources = false;
|
||||
|
||||
private boolean isDeobfuscationOn = false;
|
||||
private boolean isDeobfuscationForceSave = false;
|
||||
private boolean useSourceNameAsClassAlias = false;
|
||||
|
||||
private int deobfuscationMinLength = 0;
|
||||
private int deobfuscationMaxLength = Integer.MAX_VALUE;
|
||||
|
||||
private boolean escapeUnicode = false;
|
||||
private boolean replaceConsts = true;
|
||||
private boolean exportAsGradleProject = false;
|
||||
|
||||
public JadxArgs() {
|
||||
// use default options
|
||||
}
|
||||
|
||||
public void setRootDir(File rootDir) {
|
||||
setOutDir(rootDir);
|
||||
setOutDirSrc(new File(rootDir, DEFAULT_SRC_DIR));
|
||||
setOutDirRes(new File(rootDir, DEFAULT_RES_DIR));
|
||||
}
|
||||
|
||||
public List<File> getInputFiles() {
|
||||
return inputFiles;
|
||||
}
|
||||
|
||||
public void setInputFiles(List<File> inputFiles) {
|
||||
this.inputFiles = inputFiles;
|
||||
}
|
||||
|
||||
public File getOutDir() {
|
||||
return outDir;
|
||||
}
|
||||
|
||||
public void setOutDir(File outDir) {
|
||||
this.outDir = outDir;
|
||||
}
|
||||
|
||||
public File getOutDirSrc() {
|
||||
return outDirSrc;
|
||||
}
|
||||
|
||||
public void setOutDirSrc(File outDirSrc) {
|
||||
this.outDirSrc = outDirSrc;
|
||||
}
|
||||
|
||||
public File getOutDirRes() {
|
||||
return outDirRes;
|
||||
}
|
||||
|
||||
public void setOutDirRes(File outDirRes) {
|
||||
this.outDirRes = outDirRes;
|
||||
}
|
||||
|
||||
public int getThreadsCount() {
|
||||
return threadsCount;
|
||||
}
|
||||
|
||||
public void setThreadsCount(int threadsCount) {
|
||||
this.threadsCount = threadsCount;
|
||||
}
|
||||
|
||||
public boolean isCfgOutput() {
|
||||
return cfgOutput;
|
||||
}
|
||||
|
||||
public void setCfgOutput(boolean cfgOutput) {
|
||||
this.cfgOutput = cfgOutput;
|
||||
}
|
||||
|
||||
public boolean isRawCFGOutput() {
|
||||
return rawCFGOutput;
|
||||
}
|
||||
|
||||
public void setRawCFGOutput(boolean rawCFGOutput) {
|
||||
this.rawCFGOutput = rawCFGOutput;
|
||||
}
|
||||
|
||||
public boolean isFallbackMode() {
|
||||
return fallbackMode;
|
||||
}
|
||||
|
||||
public void setFallbackMode(boolean fallbackMode) {
|
||||
this.fallbackMode = fallbackMode;
|
||||
}
|
||||
|
||||
public boolean isShowInconsistentCode() {
|
||||
return showInconsistentCode;
|
||||
}
|
||||
|
||||
public void setShowInconsistentCode(boolean showInconsistentCode) {
|
||||
this.showInconsistentCode = showInconsistentCode;
|
||||
}
|
||||
|
||||
public boolean isUseImports() {
|
||||
return useImports;
|
||||
}
|
||||
|
||||
public void setUseImports(boolean useImports) {
|
||||
this.useImports = useImports;
|
||||
}
|
||||
|
||||
public boolean isSkipResources() {
|
||||
return isSkipResources;
|
||||
}
|
||||
|
||||
public void setSkipResources(boolean skipResources) {
|
||||
isSkipResources = skipResources;
|
||||
}
|
||||
|
||||
public boolean isSkipSources() {
|
||||
return isSkipSources;
|
||||
}
|
||||
|
||||
public void setSkipSources(boolean skipSources) {
|
||||
isSkipSources = skipSources;
|
||||
}
|
||||
|
||||
public boolean isDeobfuscationOn() {
|
||||
return isDeobfuscationOn;
|
||||
}
|
||||
|
||||
public void setDeobfuscationOn(boolean deobfuscationOn) {
|
||||
isDeobfuscationOn = deobfuscationOn;
|
||||
}
|
||||
|
||||
public boolean isDeobfuscationForceSave() {
|
||||
return isDeobfuscationForceSave;
|
||||
}
|
||||
|
||||
public void setDeobfuscationForceSave(boolean deobfuscationForceSave) {
|
||||
isDeobfuscationForceSave = deobfuscationForceSave;
|
||||
}
|
||||
|
||||
public boolean isUseSourceNameAsClassAlias() {
|
||||
return useSourceNameAsClassAlias;
|
||||
}
|
||||
|
||||
public void setUseSourceNameAsClassAlias(boolean useSourceNameAsClassAlias) {
|
||||
this.useSourceNameAsClassAlias = useSourceNameAsClassAlias;
|
||||
}
|
||||
|
||||
public int getDeobfuscationMinLength() {
|
||||
return deobfuscationMinLength;
|
||||
}
|
||||
|
||||
public void setDeobfuscationMinLength(int deobfuscationMinLength) {
|
||||
this.deobfuscationMinLength = deobfuscationMinLength;
|
||||
}
|
||||
|
||||
public int getDeobfuscationMaxLength() {
|
||||
return deobfuscationMaxLength;
|
||||
}
|
||||
|
||||
public void setDeobfuscationMaxLength(int deobfuscationMaxLength) {
|
||||
this.deobfuscationMaxLength = deobfuscationMaxLength;
|
||||
}
|
||||
|
||||
public boolean isEscapeUnicode() {
|
||||
return escapeUnicode;
|
||||
}
|
||||
|
||||
public void setEscapeUnicode(boolean escapeUnicode) {
|
||||
this.escapeUnicode = escapeUnicode;
|
||||
}
|
||||
|
||||
public boolean isReplaceConsts() {
|
||||
return replaceConsts;
|
||||
}
|
||||
|
||||
public void setReplaceConsts(boolean replaceConsts) {
|
||||
this.replaceConsts = replaceConsts;
|
||||
}
|
||||
|
||||
public boolean isExportAsGradleProject() {
|
||||
return exportAsGradleProject;
|
||||
}
|
||||
|
||||
public void setExportAsGradleProject(boolean exportAsGradleProject) {
|
||||
this.exportAsGradleProject = exportAsGradleProject;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuilder sb = new StringBuilder("JadxArgs{");
|
||||
sb.append("inputFiles=").append(inputFiles);
|
||||
sb.append(", outDir=").append(outDir);
|
||||
sb.append(", outDirSrc=").append(outDirSrc);
|
||||
sb.append(", outDirRes=").append(outDirRes);
|
||||
sb.append(", threadsCount=").append(threadsCount);
|
||||
sb.append(", cfgOutput=").append(cfgOutput);
|
||||
sb.append(", rawCFGOutput=").append(rawCFGOutput);
|
||||
sb.append(", fallbackMode=").append(fallbackMode);
|
||||
sb.append(", showInconsistentCode=").append(showInconsistentCode);
|
||||
sb.append(", useImports=").append(useImports);
|
||||
sb.append(", isSkipResources=").append(isSkipResources);
|
||||
sb.append(", isSkipSources=").append(isSkipSources);
|
||||
sb.append(", isDeobfuscationOn=").append(isDeobfuscationOn);
|
||||
sb.append(", isDeobfuscationForceSave=").append(isDeobfuscationForceSave);
|
||||
sb.append(", useSourceNameAsClassAlias=").append(useSourceNameAsClassAlias);
|
||||
sb.append(", deobfuscationMinLength=").append(deobfuscationMinLength);
|
||||
sb.append(", deobfuscationMaxLength=").append(deobfuscationMaxLength);
|
||||
sb.append(", escapeUnicode=").append(escapeUnicode);
|
||||
sb.append(", replaceConsts=").append(replaceConsts);
|
||||
sb.append(", exportAsGradleProject=").append(exportAsGradleProject);
|
||||
sb.append('}');
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
package jadx.api;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.core.utils.exceptions.JadxArgsValidateException;
|
||||
|
||||
public class JadxArgsValidator {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(JadxArgsValidator.class);
|
||||
|
||||
public static void validate(JadxArgs args) {
|
||||
checkInputFiles(args);
|
||||
validateOutDirs(args);
|
||||
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Effective jadx args: {}", args);
|
||||
}
|
||||
}
|
||||
|
||||
private static void checkInputFiles(JadxArgs args) {
|
||||
List<File> inputFiles = args.getInputFiles();
|
||||
if (inputFiles.isEmpty()) {
|
||||
throw new JadxArgsValidateException("Please specify input file");
|
||||
}
|
||||
if (inputFiles.size() > 1) {
|
||||
for (File inputFile : inputFiles) {
|
||||
String fileName = inputFile.getName();
|
||||
if (fileName.startsWith("--")) {
|
||||
throw new JadxArgsValidateException("Unknown argument: " + fileName);
|
||||
}
|
||||
}
|
||||
throw new JadxArgsValidateException("Only one input file supported");
|
||||
}
|
||||
for (File file : inputFiles) {
|
||||
checkFile(file);
|
||||
}
|
||||
}
|
||||
|
||||
private static void validateOutDirs(JadxArgs args) {
|
||||
File outDir = args.getOutDir();
|
||||
File srcDir = args.getOutDirSrc();
|
||||
File resDir = args.getOutDirRes();
|
||||
if (outDir == null) {
|
||||
if (srcDir != null) {
|
||||
outDir = srcDir;
|
||||
} else if (resDir != null) {
|
||||
outDir = resDir;
|
||||
} else {
|
||||
outDir = makeDirFromInput(args);
|
||||
}
|
||||
}
|
||||
args.setOutDir(outDir);
|
||||
setFromOut(args);
|
||||
|
||||
checkDir(args.getOutDir());
|
||||
checkDir(args.getOutDirSrc());
|
||||
checkDir(args.getOutDirRes());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static File makeDirFromInput(JadxArgs args) {
|
||||
File outDir;
|
||||
String outDirName;
|
||||
File file = args.getInputFiles().get(0);
|
||||
String name = file.getName();
|
||||
int pos = name.lastIndexOf('.');
|
||||
if (pos != -1) {
|
||||
outDirName = name.substring(0, pos);
|
||||
} else {
|
||||
outDirName = name + "-" + JadxArgs.DEFAULT_OUT_DIR;
|
||||
}
|
||||
LOG.info("output directory: {}", outDirName);
|
||||
outDir = new File(outDirName);
|
||||
return outDir;
|
||||
}
|
||||
|
||||
private static void setFromOut(JadxArgs args) {
|
||||
if (args.getOutDirSrc() == null) {
|
||||
args.setOutDirSrc(new File(args.getOutDir(), JadxArgs.DEFAULT_SRC_DIR));
|
||||
}
|
||||
if (args.getOutDirRes() == null) {
|
||||
args.setOutDirRes(new File(args.getOutDir(), JadxArgs.DEFAULT_RES_DIR));
|
||||
}
|
||||
}
|
||||
|
||||
private static void checkFile(File file) {
|
||||
if (!file.exists()) {
|
||||
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) {
|
||||
if (dir != null && dir.exists() && !dir.isDirectory()) {
|
||||
throw new JadxArgsValidateException("Output directory exists as file " + dir);
|
||||
}
|
||||
}
|
||||
|
||||
private JadxArgsValidator() {
|
||||
}
|
||||
}
|
||||
@@ -1,27 +1,13 @@
|
||||
package jadx.api;
|
||||
|
||||
import jadx.core.Jadx;
|
||||
import jadx.core.ProcessClass;
|
||||
import jadx.core.codegen.CodeGen;
|
||||
import jadx.core.codegen.CodeWriter;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.dex.visitors.IDexTreeVisitor;
|
||||
import jadx.core.dex.visitors.SaveCode;
|
||||
import jadx.core.utils.exceptions.DecodeException;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
import jadx.core.utils.files.InputFile;
|
||||
import jadx.core.xmlgen.BinaryXMLParser;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@@ -29,16 +15,35 @@ import java.util.concurrent.TimeUnit;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.core.Jadx;
|
||||
import jadx.core.ProcessClass;
|
||||
import jadx.core.codegen.CodeGen;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.dex.visitors.IDexTreeVisitor;
|
||||
import jadx.core.dex.visitors.SaveCode;
|
||||
import jadx.core.export.ExportGradleProject;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
import jadx.core.utils.files.InputFile;
|
||||
import jadx.core.xmlgen.BinaryXMLParser;
|
||||
import jadx.core.xmlgen.ResourcesSaver;
|
||||
|
||||
/**
|
||||
* Jadx API usage example:
|
||||
* <pre><code>
|
||||
* JadxDecompiler jadx = new JadxDecompiler();
|
||||
* jadx.loadFile(new File("classes.dex"));
|
||||
* jadx.setOutputDir(new File("out"));
|
||||
* jadx.save();
|
||||
* JadxArgs args = new JadxArgs();
|
||||
* args.getInputFiles().add(new File("test.apk"));
|
||||
* args.setOutDir(new File("jadx-test-output"));
|
||||
*
|
||||
* JadxDecompiler jadx = new JadxDecompiler(args);
|
||||
* jadx.load();
|
||||
* jadx.save();
|
||||
* </code></pre>
|
||||
* <p/>
|
||||
* Instead of 'save()' you can get list of decompiled classes:
|
||||
*
|
||||
* Instead of 'save()' you can iterate over decompiled classes:
|
||||
* <pre><code>
|
||||
* for(JavaClass cls : jadx.getClasses()) {
|
||||
* System.out.println(cls.getCode());
|
||||
@@ -48,10 +53,9 @@ import org.slf4j.LoggerFactory;
|
||||
public final class JadxDecompiler {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(JadxDecompiler.class);
|
||||
|
||||
private final IJadxArgs args;
|
||||
private final List<InputFile> inputFiles = new ArrayList<InputFile>();
|
||||
private JadxArgs args;
|
||||
|
||||
private File outDir;
|
||||
private final List<InputFile> inputFiles = new ArrayList<>();
|
||||
|
||||
private RootNode root;
|
||||
private List<IDexTreeVisitor> passes;
|
||||
@@ -62,28 +66,39 @@ public final class JadxDecompiler {
|
||||
|
||||
private BinaryXMLParser xmlParser;
|
||||
|
||||
private Map<ClassNode, JavaClass> classesMap = new ConcurrentHashMap<>();
|
||||
private Map<MethodNode, JavaMethod> methodsMap = new ConcurrentHashMap<>();
|
||||
private Map<FieldNode, JavaField> fieldsMap = new ConcurrentHashMap<>();
|
||||
|
||||
public JadxDecompiler() {
|
||||
this(new DefaultJadxArgs());
|
||||
this(new JadxArgs());
|
||||
}
|
||||
|
||||
public JadxDecompiler(IJadxArgs jadxArgs) {
|
||||
this.args = jadxArgs;
|
||||
this.outDir = jadxArgs.getOutDir();
|
||||
public JadxDecompiler(JadxArgs args) {
|
||||
this.args = args;
|
||||
}
|
||||
|
||||
public void load() {
|
||||
reset();
|
||||
JadxArgsValidator.validate(args);
|
||||
init();
|
||||
}
|
||||
LOG.info("loading ...");
|
||||
|
||||
public void setOutputDir(File outDir) {
|
||||
this.outDir = outDir;
|
||||
init();
|
||||
loadFiles(args.getInputFiles());
|
||||
|
||||
root = new RootNode(args);
|
||||
root.load(inputFiles);
|
||||
|
||||
root.initClassPath();
|
||||
root.loadResources(getResources());
|
||||
root.initAppResClass();
|
||||
|
||||
initVisitors();
|
||||
}
|
||||
|
||||
void init() {
|
||||
if (outDir == null) {
|
||||
outDir = new DefaultJadxArgs().getOutDir();
|
||||
}
|
||||
this.passes = Jadx.getPassesList(args, outDir);
|
||||
this.codeGen = new CodeGen(args);
|
||||
this.passes = Jadx.getPassesList(args);
|
||||
this.codeGen = new CodeGen();
|
||||
}
|
||||
|
||||
void reset() {
|
||||
@@ -99,23 +114,18 @@ public final class JadxDecompiler {
|
||||
return Jadx.getVersion();
|
||||
}
|
||||
|
||||
public void loadFile(File file) throws JadxException {
|
||||
loadFiles(Collections.singletonList(file));
|
||||
}
|
||||
|
||||
public void loadFiles(List<File> files) throws JadxException {
|
||||
private void loadFiles(List<File> files) {
|
||||
if (files.isEmpty()) {
|
||||
throw new JadxException("Empty file list");
|
||||
throw new JadxRuntimeException("Empty file list");
|
||||
}
|
||||
inputFiles.clear();
|
||||
for (File file : files) {
|
||||
try {
|
||||
inputFiles.add(new InputFile(file));
|
||||
} catch (IOException e) {
|
||||
throw new JadxException("Error load file: " + file, e);
|
||||
InputFile.addFilesFrom(file, inputFiles, args.isSkipSources());
|
||||
} catch (Exception e) {
|
||||
throw new JadxRuntimeException("Error load file: " + file, e);
|
||||
}
|
||||
}
|
||||
parse();
|
||||
}
|
||||
|
||||
public void save() {
|
||||
@@ -131,12 +141,13 @@ public final class JadxDecompiler {
|
||||
}
|
||||
|
||||
private void save(boolean saveSources, boolean saveResources) {
|
||||
ExecutorService ex = getSaveExecutor(saveSources, saveResources);
|
||||
ex.shutdown();
|
||||
try {
|
||||
ExecutorService ex = getSaveExecutor(saveSources, saveResources);
|
||||
ex.shutdown();
|
||||
ex.awaitTermination(1, TimeUnit.DAYS);
|
||||
} catch (InterruptedException e) {
|
||||
throw new JadxRuntimeException("Save interrupted", e);
|
||||
LOG.error("Save interrupted", e);
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,44 +164,61 @@ public final class JadxDecompiler {
|
||||
|
||||
LOG.info("processing ...");
|
||||
ExecutorService executor = Executors.newFixedThreadPool(threadsCount);
|
||||
|
||||
File sourcesOutDir;
|
||||
File resOutDir;
|
||||
if (args.isExportAsGradleProject()) {
|
||||
ExportGradleProject export = new ExportGradleProject(root, args.getOutDir());
|
||||
export.init();
|
||||
sourcesOutDir = export.getSrcOutDir();
|
||||
resOutDir = export.getResOutDir();
|
||||
} else {
|
||||
sourcesOutDir = args.getOutDirSrc();
|
||||
resOutDir = args.getOutDirRes();
|
||||
}
|
||||
if (saveSources) {
|
||||
for (final JavaClass cls : getClasses()) {
|
||||
executor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
cls.decompile();
|
||||
SaveCode.save(outDir, args, cls.getClassNode());
|
||||
}
|
||||
});
|
||||
}
|
||||
appendSourcesSave(executor, sourcesOutDir);
|
||||
}
|
||||
if (saveResources) {
|
||||
for (final ResourceFile resourceFile : getResources()) {
|
||||
executor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (ResourceType.isSupportedForUnpack(resourceFile.getType())) {
|
||||
CodeWriter cw = resourceFile.getContent();
|
||||
if (cw != null) {
|
||||
cw.save(new File(outDir, resourceFile.getName()));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
appendResourcesSave(executor, resOutDir);
|
||||
}
|
||||
return executor;
|
||||
}
|
||||
|
||||
private void appendResourcesSave(ExecutorService executor, File outDir) {
|
||||
for (ResourceFile resourceFile : getResources()) {
|
||||
executor.execute(new ResourcesSaver(outDir, resourceFile));
|
||||
}
|
||||
}
|
||||
|
||||
private void appendSourcesSave(ExecutorService executor, File outDir) {
|
||||
for (JavaClass cls : getClasses()) {
|
||||
if (cls.getClassNode().contains(AFlag.DONT_GENERATE)) {
|
||||
continue;
|
||||
}
|
||||
executor.execute(() -> {
|
||||
try {
|
||||
cls.decompile();
|
||||
SaveCode.save(outDir, args, cls.getClassNode());
|
||||
} catch (Exception e) {
|
||||
LOG.error("Error saving class: {}", cls.getFullName(), e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public List<JavaClass> getClasses() {
|
||||
if (root == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
if (classes == null) {
|
||||
List<ClassNode> classNodeList = root.getClasses(false);
|
||||
List<JavaClass> clsList = new ArrayList<JavaClass>(classNodeList.size());
|
||||
List<JavaClass> clsList = new ArrayList<>(classNodeList.size());
|
||||
classesMap.clear();
|
||||
for (ClassNode classNode : classNodeList) {
|
||||
clsList.add(new JavaClass(classNode, this));
|
||||
JavaClass javaClass = new JavaClass(classNode, this);
|
||||
clsList.add(javaClass);
|
||||
classesMap.put(classNode, javaClass);
|
||||
}
|
||||
classes = Collections.unmodifiableList(clsList);
|
||||
}
|
||||
@@ -212,28 +240,19 @@ public final class JadxDecompiler {
|
||||
if (classList.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
Map<String, List<JavaClass>> map = new HashMap<String, List<JavaClass>>();
|
||||
Map<String, List<JavaClass>> map = new HashMap<>();
|
||||
for (JavaClass javaClass : classList) {
|
||||
String pkg = javaClass.getPackage();
|
||||
List<JavaClass> clsList = map.get(pkg);
|
||||
if (clsList == null) {
|
||||
clsList = new ArrayList<JavaClass>();
|
||||
map.put(pkg, clsList);
|
||||
}
|
||||
List<JavaClass> clsList = map.computeIfAbsent(pkg, k -> new ArrayList<>());
|
||||
clsList.add(javaClass);
|
||||
}
|
||||
List<JavaPackage> packages = new ArrayList<JavaPackage>(map.size());
|
||||
List<JavaPackage> packages = new ArrayList<>(map.size());
|
||||
for (Map.Entry<String, List<JavaClass>> entry : map.entrySet()) {
|
||||
packages.add(new JavaPackage(entry.getKey(), entry.getValue()));
|
||||
}
|
||||
Collections.sort(packages);
|
||||
for (JavaPackage pkg : packages) {
|
||||
Collections.sort(pkg.getClasses(), new Comparator<JavaClass>() {
|
||||
@Override
|
||||
public int compare(JavaClass o1, JavaClass o2) {
|
||||
return o1.getName().compareTo(o2.getName());
|
||||
}
|
||||
});
|
||||
pkg.getClasses().sort(Comparator.comparing(JavaClass::getName));
|
||||
}
|
||||
return Collections.unmodifiableList(packages);
|
||||
}
|
||||
@@ -245,28 +264,21 @@ public final class JadxDecompiler {
|
||||
return root.getErrorsCounter().getErrorCount();
|
||||
}
|
||||
|
||||
public int getWarnsCount() {
|
||||
if (root == null) {
|
||||
return 0;
|
||||
}
|
||||
return root.getErrorsCounter().getWarnsCount();
|
||||
}
|
||||
|
||||
public void printErrorsReport() {
|
||||
if (root == null) {
|
||||
return;
|
||||
}
|
||||
root.getClsp().printMissingClasses();
|
||||
root.getErrorsCounter().printReport();
|
||||
}
|
||||
|
||||
void parse() throws DecodeException {
|
||||
reset();
|
||||
init();
|
||||
|
||||
root = new RootNode(args);
|
||||
LOG.info("loading ...");
|
||||
root.load(inputFiles);
|
||||
|
||||
root.initClassPath();
|
||||
root.loadResources(getResources());
|
||||
root.initAppResClass();
|
||||
|
||||
initVisitors();
|
||||
}
|
||||
|
||||
private void initVisitors() {
|
||||
for (IDexTreeVisitor pass : passes) {
|
||||
try {
|
||||
@@ -285,26 +297,26 @@ public final class JadxDecompiler {
|
||||
return root;
|
||||
}
|
||||
|
||||
BinaryXMLParser getXmlParser() {
|
||||
synchronized BinaryXMLParser getXmlParser() {
|
||||
if (xmlParser == null) {
|
||||
xmlParser = new BinaryXMLParser(root);
|
||||
}
|
||||
return xmlParser;
|
||||
}
|
||||
|
||||
JavaClass findJavaClass(ClassNode cls) {
|
||||
if (cls == null) {
|
||||
return null;
|
||||
}
|
||||
for (JavaClass javaClass : getClasses()) {
|
||||
if (javaClass.getClassNode().equals(cls)) {
|
||||
return javaClass;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
Map<ClassNode, JavaClass> getClassesMap() {
|
||||
return classesMap;
|
||||
}
|
||||
|
||||
public IJadxArgs getArgs() {
|
||||
Map<MethodNode, JavaMethod> getMethodsMap() {
|
||||
return methodsMap;
|
||||
}
|
||||
|
||||
Map<FieldNode, JavaField> getFieldsMap() {
|
||||
return fieldsMap;
|
||||
}
|
||||
|
||||
public JadxArgs getArgs() {
|
||||
return args;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
package jadx.api;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.core.codegen.CodeWriter;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.nodes.LineAttrNode;
|
||||
@@ -8,12 +17,6 @@ import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public final class JavaClass implements JavaNode {
|
||||
|
||||
private final JadxDecompiler decompiler;
|
||||
@@ -44,14 +47,14 @@ public final class JavaClass implements JavaNode {
|
||||
if (code == null) {
|
||||
decompile();
|
||||
code = cls.getCode();
|
||||
if (code == null) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
if (code == null) {
|
||||
return "";
|
||||
}
|
||||
return code.toString();
|
||||
return code.getCodeStr();
|
||||
}
|
||||
|
||||
public void decompile() {
|
||||
public synchronized void decompile() {
|
||||
if (decompiler == null) {
|
||||
return;
|
||||
}
|
||||
@@ -61,19 +64,25 @@ public final class JavaClass implements JavaNode {
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void unload() {
|
||||
cls.unload();
|
||||
}
|
||||
|
||||
ClassNode getClassNode() {
|
||||
return cls;
|
||||
}
|
||||
|
||||
private void load() {
|
||||
JadxDecompiler rootDecompiler = getRootDecompiler();
|
||||
int inClsCount = cls.getInnerClasses().size();
|
||||
if (inClsCount != 0) {
|
||||
List<JavaClass> list = new ArrayList<JavaClass>(inClsCount);
|
||||
List<JavaClass> list = new ArrayList<>(inClsCount);
|
||||
for (ClassNode inner : cls.getInnerClasses()) {
|
||||
if (!inner.contains(AFlag.DONT_GENERATE)) {
|
||||
JavaClass javaClass = new JavaClass(inner, this);
|
||||
javaClass.load();
|
||||
list.add(javaClass);
|
||||
rootDecompiler.getClassesMap().put(inner, javaClass);
|
||||
}
|
||||
}
|
||||
this.innerClasses = Collections.unmodifiableList(list);
|
||||
@@ -81,10 +90,12 @@ public final class JavaClass implements JavaNode {
|
||||
|
||||
int fieldsCount = cls.getFields().size();
|
||||
if (fieldsCount != 0) {
|
||||
List<JavaField> flds = new ArrayList<JavaField>(fieldsCount);
|
||||
List<JavaField> flds = new ArrayList<>(fieldsCount);
|
||||
for (FieldNode f : cls.getFields()) {
|
||||
if (!f.contains(AFlag.DONT_GENERATE)) {
|
||||
flds.add(new JavaField(f, this));
|
||||
JavaField javaField = new JavaField(f, this);
|
||||
flds.add(javaField);
|
||||
rootDecompiler.getFieldsMap().put(f, javaField);
|
||||
}
|
||||
}
|
||||
this.fields = Collections.unmodifiableList(flds);
|
||||
@@ -92,54 +103,98 @@ public final class JavaClass implements JavaNode {
|
||||
|
||||
int methodsCount = cls.getMethods().size();
|
||||
if (methodsCount != 0) {
|
||||
List<JavaMethod> mths = new ArrayList<JavaMethod>(methodsCount);
|
||||
List<JavaMethod> mths = new ArrayList<>(methodsCount);
|
||||
for (MethodNode m : cls.getMethods()) {
|
||||
if (!m.contains(AFlag.DONT_GENERATE)) {
|
||||
mths.add(new JavaMethod(this, m));
|
||||
JavaMethod javaMethod = new JavaMethod(this, m);
|
||||
mths.add(javaMethod);
|
||||
rootDecompiler.getMethodsMap().put(m, javaMethod);
|
||||
}
|
||||
}
|
||||
Collections.sort(mths, new Comparator<JavaMethod>() {
|
||||
@Override
|
||||
public int compare(JavaMethod o1, JavaMethod o2) {
|
||||
return o1.getName().compareTo(o2.getName());
|
||||
}
|
||||
});
|
||||
mths.sort(Comparator.comparing(JavaMethod::getName));
|
||||
this.methods = Collections.unmodifiableList(mths);
|
||||
}
|
||||
}
|
||||
|
||||
private Map<CodePosition, Object> getCodeAnnotations() {
|
||||
decompile();
|
||||
return cls.getCode().getAnnotations();
|
||||
private JadxDecompiler getRootDecompiler() {
|
||||
if (parent != null) {
|
||||
return parent.getRootDecompiler();
|
||||
}
|
||||
return decompiler;
|
||||
}
|
||||
|
||||
public CodePosition getDefinitionPosition(int line, int offset) {
|
||||
private Map<CodePosition, Object> getCodeAnnotations() {
|
||||
decompile();
|
||||
CodeWriter code = cls.getCode();
|
||||
if (code == null) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
return code.getAnnotations();
|
||||
}
|
||||
|
||||
public Map<CodePosition, JavaNode> getUsageMap() {
|
||||
Map<CodePosition, Object> map = getCodeAnnotations();
|
||||
if (map.isEmpty() || decompiler == null) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
Map<CodePosition, JavaNode> resultMap = new HashMap<>(map.size());
|
||||
for (Map.Entry<CodePosition, Object> entry : map.entrySet()) {
|
||||
CodePosition codePosition = entry.getKey();
|
||||
Object obj = entry.getValue();
|
||||
if (obj instanceof LineAttrNode) {
|
||||
JavaNode node = convertNode(obj);
|
||||
if (node != null) {
|
||||
resultMap.put(codePosition, node);
|
||||
}
|
||||
}
|
||||
}
|
||||
return resultMap;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
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().getMethodsMap().get(obj);
|
||||
}
|
||||
if (obj instanceof FieldNode) {
|
||||
return getRootDecompiler().getFieldsMap().get(obj);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public JavaNode getJavaNodeAtPosition(int line, int offset) {
|
||||
Map<CodePosition, Object> map = getCodeAnnotations();
|
||||
if (map.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
Object obj = map.get(new CodePosition(line, offset));
|
||||
if (!(obj instanceof LineAttrNode)) {
|
||||
if (obj == null) {
|
||||
return null;
|
||||
}
|
||||
ClassNode clsNode = null;
|
||||
if (obj instanceof ClassNode) {
|
||||
clsNode = (ClassNode) obj;
|
||||
} else if (obj instanceof MethodNode) {
|
||||
clsNode = ((MethodNode) obj).getParentClass();
|
||||
} else if (obj instanceof FieldNode) {
|
||||
clsNode = ((FieldNode) obj).getParentClass();
|
||||
}
|
||||
if (clsNode == null) {
|
||||
return null;
|
||||
}
|
||||
clsNode = clsNode.getTopParentClass();
|
||||
JavaClass jCls = decompiler.findJavaClass(clsNode);
|
||||
if (jCls == null) {
|
||||
return convertNode(obj);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public CodePosition getDefinitionPosition(int line, int offset) {
|
||||
JavaNode javaNode = getJavaNodeAtPosition(line, offset);
|
||||
if (javaNode == null) {
|
||||
return null;
|
||||
}
|
||||
return getDefinitionPosition(javaNode);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public CodePosition getDefinitionPosition(JavaNode javaNode) {
|
||||
JavaClass jCls = javaNode.getTopParentClass();
|
||||
jCls.decompile();
|
||||
int defLine = ((LineAttrNode) obj).getDecompiledLine();
|
||||
int defLine = javaNode.getDecompiledLine();
|
||||
if (defLine == 0) {
|
||||
return null;
|
||||
}
|
||||
@@ -170,6 +225,11 @@ public final class JavaClass implements JavaNode {
|
||||
return parent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaClass getTopParentClass() {
|
||||
return parent == null ? this : parent.getTopParentClass();
|
||||
}
|
||||
|
||||
public AccessInfo getAccessInfo() {
|
||||
return cls.getAccessFlags();
|
||||
}
|
||||
|
||||
@@ -29,6 +29,11 @@ public final class JavaField implements JavaNode {
|
||||
return parent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaClass getTopParentClass() {
|
||||
return parent.getTopParentClass();
|
||||
}
|
||||
|
||||
public AccessInfo getAccessFlags() {
|
||||
return field.getAccessFlags();
|
||||
}
|
||||
@@ -40,4 +45,19 @@ public final class JavaField implements JavaNode {
|
||||
public int getDecompiledLine() {
|
||||
return field.getDecompiledLine();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return field.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return this == o || o instanceof JavaField && field.equals(((JavaField) o).field);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return field.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package jadx.api;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import jadx.core.dex.info.AccessInfo;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public final class JavaMethod implements JavaNode {
|
||||
private final MethodNode mth;
|
||||
private final JavaClass parent;
|
||||
@@ -30,6 +30,11 @@ public final class JavaMethod implements JavaNode {
|
||||
return parent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaClass getTopParentClass() {
|
||||
return parent.getTopParentClass();
|
||||
}
|
||||
|
||||
public AccessInfo getAccessFlags() {
|
||||
return mth.getAccessFlags();
|
||||
}
|
||||
@@ -53,4 +58,19 @@ public final class JavaMethod implements JavaNode {
|
||||
public int getDecompiledLine() {
|
||||
return mth.getDecompiledLine();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return mth.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return this == o || o instanceof JavaMethod && mth.equals(((JavaMethod) o).mth);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return mth.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,4 +7,8 @@ public interface JavaNode {
|
||||
String getFullName();
|
||||
|
||||
JavaClass getDeclaringClass();
|
||||
|
||||
JavaClass getTopParentClass();
|
||||
|
||||
int getDecompiledLine();
|
||||
}
|
||||
|
||||
@@ -33,6 +33,16 @@ public final class JavaPackage implements JavaNode, Comparable<JavaPackage> {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaClass getTopParentClass() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDecompiledLine() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull JavaPackage o) {
|
||||
return name.compareTo(o.name);
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
package jadx.api;
|
||||
|
||||
import jadx.core.codegen.CodeWriter;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import jadx.core.utils.files.ZipSecurity;
|
||||
import jadx.core.xmlgen.ResContainer;
|
||||
|
||||
public class ResourceFile {
|
||||
|
||||
public static final class ZipRef {
|
||||
@@ -34,7 +35,7 @@ public class ResourceFile {
|
||||
private final ResourceType type;
|
||||
private ZipRef zipRef;
|
||||
|
||||
ResourceFile(JadxDecompiler decompiler, String name, ResourceType type) {
|
||||
protected ResourceFile(JadxDecompiler decompiler, String name, ResourceType type) {
|
||||
this.decompiler = decompiler;
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
@@ -48,7 +49,7 @@ public class ResourceFile {
|
||||
return type;
|
||||
}
|
||||
|
||||
public CodeWriter getContent() {
|
||||
public ResContainer loadContent() {
|
||||
return ResourcesLoader.loadContent(decompiler, this);
|
||||
}
|
||||
|
||||
@@ -56,7 +57,7 @@ public class ResourceFile {
|
||||
this.zipRef = zipRef;
|
||||
}
|
||||
|
||||
ZipRef getZipRef() {
|
||||
public ZipRef getZipRef() {
|
||||
return zipRef;
|
||||
}
|
||||
|
||||
@@ -64,4 +65,11 @@ public class ResourceFile {
|
||||
public String toString() {
|
||||
return "ResourceFile{name='" + name + '\'' + ", type=" + type + "}";
|
||||
}
|
||||
|
||||
public static ResourceFile createResourceFileInstance(JadxDecompiler decompiler, String name, ResourceType type) {
|
||||
if(!ZipSecurity.isValidZipEntryName(name)) {
|
||||
return null;
|
||||
}
|
||||
return new ResourceFile(decompiler, name, type);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package jadx.api;
|
||||
|
||||
import jadx.core.codegen.CodeWriter;
|
||||
import jadx.core.utils.files.ZipSecurity;
|
||||
import jadx.core.xmlgen.ResContainer;
|
||||
|
||||
public class ResourceFileContent extends ResourceFile {
|
||||
|
||||
private final CodeWriter content;
|
||||
|
||||
private ResourceFileContent(String name, ResourceType type, CodeWriter content) {
|
||||
super(null, name, type);
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResContainer loadContent() {
|
||||
return ResContainer.singleFile(getName(), content);
|
||||
}
|
||||
|
||||
public static ResourceFileContent createResourceFileContentInstance(String name, ResourceType type, CodeWriter content) {
|
||||
if(!ZipSecurity.isValidZipEntryName(name)) {
|
||||
return null;
|
||||
}
|
||||
return new ResourceFileContent(name, type, content);
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
package jadx.api;
|
||||
|
||||
public enum ResourceType {
|
||||
CODE(".dex", ".class"),
|
||||
CODE(".dex", ".jar", ".class"),
|
||||
MANIFEST("AndroidManifest.xml"),
|
||||
XML(".xml"), // TODO binary or not?
|
||||
ARSC(".arsc"), // TODO decompile !!!
|
||||
XML(".xml"),
|
||||
ARSC(".arsc"),
|
||||
FONT(".ttf"),
|
||||
IMG(".png", ".gif", ".jpg"),
|
||||
LIB(".so"),
|
||||
@@ -34,15 +34,15 @@ public enum ResourceType {
|
||||
public static boolean isSupportedForUnpack(ResourceType type) {
|
||||
switch (type) {
|
||||
case CODE:
|
||||
case ARSC:
|
||||
case LIB:
|
||||
case FONT:
|
||||
case IMG:
|
||||
case UNKNOWN:
|
||||
return false;
|
||||
|
||||
case MANIFEST:
|
||||
case XML:
|
||||
case ARSC:
|
||||
case IMG:
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -1,15 +1,9 @@
|
||||
package jadx.api;
|
||||
|
||||
import jadx.api.ResourceFile.ZipRef;
|
||||
import jadx.core.codegen.CodeWriter;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
import jadx.core.utils.files.InputFile;
|
||||
import jadx.core.xmlgen.ResTableParser;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
@@ -21,11 +15,22 @@ import java.util.zip.ZipFile;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.ResourceFile.ZipRef;
|
||||
import jadx.core.codegen.CodeWriter;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
import jadx.core.utils.files.InputFile;
|
||||
import jadx.core.utils.files.ZipSecurity;
|
||||
import jadx.core.xmlgen.ResContainer;
|
||||
import jadx.core.xmlgen.ResTableParser;
|
||||
|
||||
import static jadx.core.utils.files.FileUtils.READ_BUFFER_SIZE;
|
||||
import static jadx.core.utils.files.FileUtils.copyStream;
|
||||
|
||||
// TODO: move to core package
|
||||
public final class ResourcesLoader {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ResourcesLoader.class);
|
||||
|
||||
private static final int READ_BUFFER_SIZE = 8 * 1024;
|
||||
private static final int LOAD_SIZE_LIMIT = 10 * 1024 * 1024;
|
||||
|
||||
private final JadxDecompiler jadxRef;
|
||||
@@ -35,7 +40,7 @@ public final class ResourcesLoader {
|
||||
}
|
||||
|
||||
List<ResourceFile> load(List<InputFile> inputFiles) {
|
||||
List<ResourceFile> list = new ArrayList<ResourceFile>(inputFiles.size());
|
||||
List<ResourceFile> list = new ArrayList<>(inputFiles.size());
|
||||
for (InputFile file : inputFiles) {
|
||||
loadFile(list, file.getFile());
|
||||
}
|
||||
@@ -43,96 +48,96 @@ public final class ResourcesLoader {
|
||||
}
|
||||
|
||||
public interface ResourceDecoder {
|
||||
Object decode(long size, InputStream is) throws IOException;
|
||||
ResContainer decode(long size, InputStream is) throws IOException;
|
||||
}
|
||||
|
||||
public static Object decodeStream(ResourceFile rf, ResourceDecoder decoder) throws JadxException {
|
||||
ZipRef zipRef = rf.getZipRef();
|
||||
if (zipRef == null) {
|
||||
return null;
|
||||
}
|
||||
ZipFile zipFile = null;
|
||||
InputStream inputStream = null;
|
||||
public static ResContainer decodeStream(ResourceFile rf, ResourceDecoder decoder) throws JadxException {
|
||||
try {
|
||||
zipFile = new ZipFile(zipRef.getZipFile());
|
||||
ZipEntry entry = zipFile.getEntry(zipRef.getEntryName());
|
||||
if (entry == null) {
|
||||
throw new IOException("Zip entry not found: " + zipRef);
|
||||
}
|
||||
inputStream = new BufferedInputStream(zipFile.getInputStream(entry));
|
||||
return decoder.decode(entry.getSize(), inputStream);
|
||||
} catch (Exception e) {
|
||||
throw new JadxException("Error decode: " + zipRef.getEntryName(), e);
|
||||
} finally {
|
||||
try {
|
||||
if (zipFile != null) {
|
||||
zipFile.close();
|
||||
ZipRef zipRef = rf.getZipRef();
|
||||
if (zipRef == null) {
|
||||
File file = new File(rf.getName());
|
||||
try (InputStream inputStream = new BufferedInputStream(new FileInputStream(file))) {
|
||||
return decoder.decode(file.length(), inputStream);
|
||||
}
|
||||
if (inputStream != null) {
|
||||
inputStream.close();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.debug("Error close zip file: {}", zipRef, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static CodeWriter loadContent(final JadxDecompiler jadxRef, final ResourceFile rf) {
|
||||
try {
|
||||
return (CodeWriter) decodeStream(rf, new ResourceDecoder() {
|
||||
@Override
|
||||
public Object decode(long size, InputStream is) throws IOException {
|
||||
if (size > LOAD_SIZE_LIMIT) {
|
||||
return new CodeWriter().add("File too big, size: "
|
||||
+ String.format("%.2f KB", size / 1024.));
|
||||
} else {
|
||||
try (ZipFile zipFile = new ZipFile(zipRef.getZipFile())) {
|
||||
ZipEntry entry = zipFile.getEntry(zipRef.getEntryName());
|
||||
if (entry == null) {
|
||||
throw new IOException("Zip entry not found: " + zipRef);
|
||||
}
|
||||
if (!ZipSecurity.isValidZipEntry(entry)) {
|
||||
return null;
|
||||
}
|
||||
try (InputStream inputStream = new BufferedInputStream(zipFile.getInputStream(entry))) {
|
||||
return decoder.decode(entry.getSize(), inputStream);
|
||||
}
|
||||
return loadContent(jadxRef, rf.getType(), is);
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new JadxException("Error decode: " + rf.getName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
static ResContainer loadContent(JadxDecompiler jadxRef, ResourceFile rf) {
|
||||
try {
|
||||
return decodeStream(rf, (size, is) -> loadContent(jadxRef, rf, is, size));
|
||||
} catch (JadxException e) {
|
||||
LOG.error("Decode error", e);
|
||||
CodeWriter cw = new CodeWriter();
|
||||
cw.add("Error decode ").add(rf.getType().toString().toLowerCase());
|
||||
cw.startLine(Utils.getStackTrace(e.getCause()));
|
||||
return cw;
|
||||
return ResContainer.singleFile(rf.getName(), cw);
|
||||
}
|
||||
}
|
||||
|
||||
private static CodeWriter loadContent(JadxDecompiler jadxRef, ResourceType type,
|
||||
InputStream inputStream) throws IOException {
|
||||
switch (type) {
|
||||
private static ResContainer loadContent(JadxDecompiler jadxRef, ResourceFile rf,
|
||||
InputStream inputStream, long size) throws IOException {
|
||||
switch (rf.getType()) {
|
||||
case MANIFEST:
|
||||
case XML:
|
||||
return jadxRef.getXmlParser().parse(inputStream);
|
||||
return ResContainer.singleFile(rf.getName(),
|
||||
jadxRef.getXmlParser().parse(inputStream));
|
||||
|
||||
case ARSC:
|
||||
return new ResTableParser().decodeToCodeWriter(inputStream);
|
||||
return new ResTableParser()
|
||||
.decodeFiles(inputStream);
|
||||
|
||||
case IMG:
|
||||
return ResContainer.singleImageFile(rf.getName(), inputStream);
|
||||
|
||||
default:
|
||||
if (size > LOAD_SIZE_LIMIT) {
|
||||
return ResContainer.singleFile(rf.getName(),
|
||||
new CodeWriter().add("File too big, size: " + String.format("%.2f KB", size / 1024.)));
|
||||
}
|
||||
return ResContainer.singleFile(rf.getName(), loadToCodeWriter(inputStream));
|
||||
}
|
||||
return loadToCodeWriter(inputStream);
|
||||
}
|
||||
|
||||
private void loadFile(List<ResourceFile> list, File file) {
|
||||
if (file == null) {
|
||||
return;
|
||||
}
|
||||
ZipFile zip = null;
|
||||
try {
|
||||
zip = new ZipFile(file);
|
||||
try (ZipFile zip = new ZipFile(file)) {
|
||||
Enumeration<? extends ZipEntry> entries = zip.entries();
|
||||
while (entries.hasMoreElements()) {
|
||||
ZipEntry entry = entries.nextElement();
|
||||
addEntry(list, file, entry);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.debug("Not a zip file: {}", file.getAbsolutePath());
|
||||
} finally {
|
||||
if (zip != null) {
|
||||
try {
|
||||
zip.close();
|
||||
} catch (Exception e) {
|
||||
LOG.error("Zip file close error: {}", file.getAbsolutePath(), e);
|
||||
if (ZipSecurity.isValidZipEntry(entry)) {
|
||||
addEntry(list, file, entry);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.debug("Not a zip file: {}", file.getAbsolutePath());
|
||||
addResourceFile(list, file);
|
||||
}
|
||||
}
|
||||
|
||||
private void addResourceFile(List<ResourceFile> list, File file) {
|
||||
String name = file.getAbsolutePath();
|
||||
ResourceType type = ResourceType.getFileType(name);
|
||||
ResourceFile rf = ResourceFile.createResourceFileInstance(jadxRef, name, type);
|
||||
if (rf != null) {
|
||||
list.add(rf);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,27 +147,17 @@ public final class ResourcesLoader {
|
||||
}
|
||||
String name = entry.getName();
|
||||
ResourceType type = ResourceType.getFileType(name);
|
||||
ResourceFile rf = new ResourceFile(jadxRef, name, type);
|
||||
rf.setZipRef(new ZipRef(zipFile, name));
|
||||
list.add(rf);
|
||||
// LOG.debug("Add resource entry: {}, size: {}", name, entry.getSize());
|
||||
ResourceFile rf = ResourceFile.createResourceFileInstance(jadxRef, name, type);
|
||||
if (rf != null) {
|
||||
rf.setZipRef(new ZipRef(zipFile, name));
|
||||
list.add(rf);
|
||||
}
|
||||
}
|
||||
|
||||
private static CodeWriter loadToCodeWriter(InputStream is) throws IOException {
|
||||
public static CodeWriter loadToCodeWriter(InputStream is) throws IOException {
|
||||
CodeWriter cw = new CodeWriter();
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(READ_BUFFER_SIZE);
|
||||
byte[] buffer = new byte[READ_BUFFER_SIZE];
|
||||
int count;
|
||||
try {
|
||||
while ((count = is.read(buffer)) != -1) {
|
||||
baos.write(buffer, 0, count);
|
||||
}
|
||||
} finally {
|
||||
try {
|
||||
is.close();
|
||||
} catch (Exception ignore) {
|
||||
}
|
||||
}
|
||||
copyStream(is, baos);
|
||||
cw.add(baos.toString("UTF-8"));
|
||||
return cw;
|
||||
}
|
||||
|
||||
@@ -21,4 +21,7 @@ public class Consts {
|
||||
public static final String ANONYMOUS_CLASS_PREFIX = "AnonymousClass";
|
||||
|
||||
public static final String MTH_TOSTRING_SIGNATURE = "toString()Ljava/lang/String;";
|
||||
|
||||
private Consts() {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
package jadx.core;
|
||||
|
||||
import jadx.api.IJadxArgs;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import java.util.jar.Manifest;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.core.dex.visitors.ClassModifier;
|
||||
import jadx.core.dex.visitors.CodeShrinker;
|
||||
import jadx.core.dex.visitors.ConstInlineVisitor;
|
||||
@@ -8,6 +17,7 @@ import jadx.core.dex.visitors.DebugInfoVisitor;
|
||||
import jadx.core.dex.visitors.DependencyCollector;
|
||||
import jadx.core.dex.visitors.DotGraphVisitor;
|
||||
import jadx.core.dex.visitors.EnumVisitor;
|
||||
import jadx.core.dex.visitors.ExtractFieldInit;
|
||||
import jadx.core.dex.visitors.FallbackModeVisitor;
|
||||
import jadx.core.dex.visitors.IDexTreeVisitor;
|
||||
import jadx.core.dex.visitors.MethodInlineVisitor;
|
||||
@@ -31,32 +41,21 @@ import jadx.core.dex.visitors.ssa.EliminatePhiNodes;
|
||||
import jadx.core.dex.visitors.ssa.SSATransform;
|
||||
import jadx.core.dex.visitors.typeinference.FinishTypeInference;
|
||||
import jadx.core.dex.visitors.typeinference.TypeInference;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import java.util.jar.Manifest;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class Jadx {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(Jadx.class);
|
||||
|
||||
private Jadx() {
|
||||
}
|
||||
|
||||
static {
|
||||
if (Consts.DEBUG) {
|
||||
LOG.info("debug enabled");
|
||||
}
|
||||
if (Jadx.class.desiredAssertionStatus()) {
|
||||
LOG.info("assertions enabled");
|
||||
}
|
||||
}
|
||||
|
||||
public static List<IDexTreeVisitor> getPassesList(IJadxArgs args, File outDir) {
|
||||
List<IDexTreeVisitor> passes = new ArrayList<IDexTreeVisitor>();
|
||||
public static List<IDexTreeVisitor> getPassesList(JadxArgs args) {
|
||||
List<IDexTreeVisitor> passes = new ArrayList<>();
|
||||
if (args.isFallbackMode()) {
|
||||
passes.add(new FallbackModeVisitor());
|
||||
} else {
|
||||
@@ -71,7 +70,7 @@ public class Jadx {
|
||||
passes.add(new TypeInference());
|
||||
|
||||
if (args.isRawCFGOutput()) {
|
||||
passes.add(DotGraphVisitor.dumpRaw(outDir));
|
||||
passes.add(DotGraphVisitor.dumpRaw());
|
||||
}
|
||||
|
||||
passes.add(new ConstInlineVisitor());
|
||||
@@ -83,8 +82,8 @@ public class Jadx {
|
||||
passes.add(new CodeShrinker());
|
||||
passes.add(new ReSugarCode());
|
||||
|
||||
if (args.isCFGOutput()) {
|
||||
passes.add(DotGraphVisitor.dump(outDir));
|
||||
if (args.isCfgOutput()) {
|
||||
passes.add(DotGraphVisitor.dump());
|
||||
}
|
||||
|
||||
passes.add(new RegionMakerVisitor());
|
||||
@@ -95,17 +94,18 @@ public class Jadx {
|
||||
passes.add(new SimplifyVisitor());
|
||||
passes.add(new CheckRegions());
|
||||
|
||||
if (args.isCFGOutput()) {
|
||||
passes.add(DotGraphVisitor.dumpRegions(outDir));
|
||||
}
|
||||
|
||||
passes.add(new MethodInlineVisitor());
|
||||
passes.add(new ExtractFieldInit());
|
||||
passes.add(new ClassModifier());
|
||||
passes.add(new EnumVisitor());
|
||||
passes.add(new PrepareForCodeGen());
|
||||
passes.add(new LoopRegionVisitor());
|
||||
passes.add(new ProcessVariables());
|
||||
|
||||
if (args.isCfgOutput()) {
|
||||
passes.add(DotGraphVisitor.dumpRegions());
|
||||
}
|
||||
|
||||
passes.add(new DependencyCollector());
|
||||
|
||||
passes.add(new RenameVisitor());
|
||||
@@ -115,7 +115,7 @@ public class Jadx {
|
||||
|
||||
public static String getVersion() {
|
||||
try {
|
||||
ClassLoader classLoader = Utils.class.getClassLoader();
|
||||
ClassLoader classLoader = Jadx.class.getClassLoader();
|
||||
if (classLoader != null) {
|
||||
Enumeration<URL> resources = classLoader.getResources("META-INF/MANIFEST.MF");
|
||||
while (resources.hasMoreElements()) {
|
||||
|
||||
@@ -1,25 +1,20 @@
|
||||
package jadx.core;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.core.codegen.CodeGen;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.visitors.DepthTraversal;
|
||||
import jadx.core.dex.visitors.IDexTreeVisitor;
|
||||
import jadx.core.utils.ErrorsCounter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import static jadx.core.dex.nodes.ProcessState.GENERATED;
|
||||
import static jadx.core.dex.nodes.ProcessState.NOT_LOADED;
|
||||
import static jadx.core.dex.nodes.ProcessState.PROCESSED;
|
||||
import static jadx.core.dex.nodes.ProcessState.STARTED;
|
||||
import static jadx.core.dex.nodes.ProcessState.UNLOADED;
|
||||
|
||||
public final class ProcessClass {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ProcessClass.class);
|
||||
|
||||
private ProcessClass() {
|
||||
}
|
||||
@@ -28,7 +23,7 @@ public final class ProcessClass {
|
||||
if (codeGen == null && cls.getState() == PROCESSED) {
|
||||
return;
|
||||
}
|
||||
synchronized (cls) {
|
||||
synchronized (getSyncObj(cls)) {
|
||||
try {
|
||||
if (cls.getState() == NOT_LOADED) {
|
||||
cls.load();
|
||||
@@ -41,22 +36,18 @@ public final class ProcessClass {
|
||||
if (cls.getState() == PROCESSED && codeGen != null) {
|
||||
processDependencies(cls, passes);
|
||||
codeGen.visit(cls);
|
||||
cls.setState(GENERATED);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
ErrorsCounter.classError(cls, e.getClass().getSimpleName(), e);
|
||||
} finally {
|
||||
if (cls.getState() == GENERATED) {
|
||||
cls.unload();
|
||||
cls.setState(UNLOADED);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void processDependencies(ClassNode cls, List<IDexTreeVisitor> passes) {
|
||||
for (ClassNode depCls : cls.getDependencies()) {
|
||||
process(depCls, passes, null);
|
||||
}
|
||||
public static Object getSyncObj(ClassNode cls) {
|
||||
return cls.getClassInfo();
|
||||
}
|
||||
|
||||
private static void processDependencies(ClassNode cls, List<IDexTreeVisitor> passes) {
|
||||
cls.getDependencies().forEach(depCls -> process(depCls, passes, null));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,5 @@
|
||||
package jadx.core.clsp;
|
||||
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.exceptions.DecodeException;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
import jadx.core.utils.files.FileUtils;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
@@ -27,6 +20,16 @@ import java.util.zip.ZipOutputStream;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.exceptions.DecodeException;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
import jadx.core.utils.files.FileUtils;
|
||||
import jadx.core.utils.files.ZipSecurity;
|
||||
|
||||
import static jadx.core.utils.files.FileUtils.close;
|
||||
|
||||
/**
|
||||
* Classes list for import into classpath graph
|
||||
*/
|
||||
@@ -46,7 +49,7 @@ public class ClsSet {
|
||||
|
||||
public void load(RootNode root) {
|
||||
List<ClassNode> list = root.getClasses(true);
|
||||
Map<String, NClass> names = new HashMap<String, NClass>(list.size());
|
||||
Map<String, NClass> names = new HashMap<>(list.size());
|
||||
int k = 0;
|
||||
for (ClassNode cls : list) {
|
||||
String clsRawName = cls.getRawName();
|
||||
@@ -76,7 +79,7 @@ public class ClsSet {
|
||||
}
|
||||
|
||||
public static NClass[] makeParentsArray(ClassNode cls, Map<String, NClass> names) {
|
||||
List<NClass> parents = new ArrayList<NClass>(1 + cls.getInterfaces().size());
|
||||
List<NClass> parents = new ArrayList<>(1 + cls.getInterfaces().size());
|
||||
ArgType superClass = cls.getSuperClass();
|
||||
if (superClass != null) {
|
||||
NClass c = getCls(superClass.getObject(), names);
|
||||
@@ -103,9 +106,7 @@ public class ClsSet {
|
||||
|
||||
void save(File output) throws IOException {
|
||||
FileUtils.makeDirsForFile(output);
|
||||
|
||||
BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(output));
|
||||
try {
|
||||
try (BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(output))) {
|
||||
String outputName = output.getName();
|
||||
if (outputName.endsWith(CLST_EXTENSION)) {
|
||||
save(outputStream);
|
||||
@@ -115,19 +116,16 @@ public class ClsSet {
|
||||
out.putNextEntry(new ZipEntry(CLST_PKG_PATH + "/" + CLST_FILENAME));
|
||||
save(out);
|
||||
} finally {
|
||||
out.close();
|
||||
close(out);
|
||||
}
|
||||
} else {
|
||||
throw new JadxRuntimeException("Unknown file format: " + outputName);
|
||||
}
|
||||
} finally {
|
||||
outputStream.close();
|
||||
}
|
||||
}
|
||||
|
||||
public void save(OutputStream output) throws IOException {
|
||||
DataOutputStream out = new DataOutputStream(output);
|
||||
try {
|
||||
try (DataOutputStream out = new DataOutputStream(output)) {
|
||||
out.writeBytes(JADX_CLS_SET_HEADER);
|
||||
out.writeByte(VERSION);
|
||||
|
||||
@@ -143,53 +141,41 @@ public class ClsSet {
|
||||
out.writeInt(parent.getId());
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
out.close();
|
||||
}
|
||||
}
|
||||
|
||||
public void load() throws IOException, DecodeException {
|
||||
InputStream input = getClass().getResourceAsStream(CLST_FILENAME);
|
||||
if (input == null) {
|
||||
throw new JadxRuntimeException("Can't load classpath file: " + CLST_FILENAME);
|
||||
}
|
||||
try {
|
||||
try (InputStream input = getClass().getResourceAsStream(CLST_FILENAME)) {
|
||||
if (input == null) {
|
||||
throw new JadxRuntimeException("Can't load classpath file: " + CLST_FILENAME);
|
||||
}
|
||||
load(input);
|
||||
} finally {
|
||||
input.close();
|
||||
}
|
||||
}
|
||||
|
||||
public void load(File input) throws IOException, DecodeException {
|
||||
String name = input.getName();
|
||||
InputStream inputStream = new FileInputStream(input);
|
||||
try {
|
||||
try (InputStream inputStream = new FileInputStream(input)) {
|
||||
if (name.endsWith(CLST_EXTENSION)) {
|
||||
load(inputStream);
|
||||
} else if (name.endsWith(".jar")) {
|
||||
ZipInputStream in = new ZipInputStream(inputStream);
|
||||
try {
|
||||
try (ZipInputStream in = new ZipInputStream(inputStream)) {
|
||||
ZipEntry entry = in.getNextEntry();
|
||||
while (entry != null) {
|
||||
if (entry.getName().endsWith(CLST_EXTENSION)) {
|
||||
if (entry.getName().endsWith(CLST_EXTENSION) && ZipSecurity.isValidZipEntry(entry)) {
|
||||
load(in);
|
||||
}
|
||||
entry = in.getNextEntry();
|
||||
}
|
||||
} finally {
|
||||
in.close();
|
||||
}
|
||||
} else {
|
||||
throw new JadxRuntimeException("Unknown file format: " + name);
|
||||
}
|
||||
} finally {
|
||||
inputStream.close();
|
||||
}
|
||||
}
|
||||
|
||||
public void load(InputStream input) throws IOException, DecodeException {
|
||||
DataInputStream in = new DataInputStream(input);
|
||||
try {
|
||||
try (DataInputStream in = new DataInputStream(input)) {
|
||||
byte[] header = new byte[JADX_CLS_SET_HEADER.length()];
|
||||
int readHeaderLength = in.read(header);
|
||||
int version = in.readByte();
|
||||
@@ -212,8 +198,6 @@ public class ClsSet {
|
||||
}
|
||||
classes[i].setParents(parents);
|
||||
}
|
||||
} finally {
|
||||
in.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
package jadx.core.clsp;
|
||||
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.utils.exceptions.DecodeException;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
@@ -16,15 +13,21 @@ import java.util.WeakHashMap;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.utils.exceptions.DecodeException;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
/**
|
||||
* Classes hierarchy graph
|
||||
*/
|
||||
public class ClspGraph {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ClspGraph.class);
|
||||
|
||||
private final Map<String, Set<String>> ancestorCache = new WeakHashMap<String, Set<String>>();
|
||||
private final Map<String, Set<String>> ancestorCache = Collections.synchronizedMap(new WeakHashMap<String, Set<String>>());
|
||||
private Map<String, NClass> nameMap;
|
||||
|
||||
private final Set<String> missingClasses = new HashSet<>();
|
||||
|
||||
public void load() throws IOException, DecodeException {
|
||||
ClsSet set = new ClsSet();
|
||||
set.load();
|
||||
@@ -33,7 +36,7 @@ public class ClspGraph {
|
||||
|
||||
public void addClasspath(ClsSet set) {
|
||||
if (nameMap == null) {
|
||||
nameMap = new HashMap<String, NClass>(set.getClassesCount());
|
||||
nameMap = new HashMap<>(set.getClassesCount());
|
||||
set.addToMap(nameMap);
|
||||
} else {
|
||||
throw new JadxRuntimeException("Classpath already loaded");
|
||||
@@ -73,7 +76,7 @@ public class ClspGraph {
|
||||
}
|
||||
NClass cls = nameMap.get(implClsName);
|
||||
if (cls == null) {
|
||||
LOG.debug("Missing class: {}", implClsName);
|
||||
missingClasses.add(clsName);
|
||||
return null;
|
||||
}
|
||||
if (isImplements(clsName, implClsName)) {
|
||||
@@ -104,10 +107,10 @@ public class ClspGraph {
|
||||
}
|
||||
NClass cls = nameMap.get(clsName);
|
||||
if (cls == null) {
|
||||
LOG.debug("Missing class: {}", clsName);
|
||||
missingClasses.add(clsName);
|
||||
return Collections.emptySet();
|
||||
}
|
||||
result = new HashSet<String>();
|
||||
result = new HashSet<>();
|
||||
addAncestorsNames(cls, result);
|
||||
if (result.isEmpty()) {
|
||||
result = Collections.emptySet();
|
||||
@@ -117,9 +120,26 @@ public class ClspGraph {
|
||||
}
|
||||
|
||||
private void addAncestorsNames(NClass cls, Set<String> result) {
|
||||
result.add(cls.getName());
|
||||
for (NClass p : cls.getParents()) {
|
||||
addAncestorsNames(p, result);
|
||||
boolean isNew = result.add(cls.getName());
|
||||
if (isNew) {
|
||||
for (NClass p : cls.getParents()) {
|
||||
addAncestorsNames(p, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void printMissingClasses() {
|
||||
int count = missingClasses.size();
|
||||
if (count == 0) {
|
||||
return;
|
||||
}
|
||||
LOG.warn("Found {} references to unknown classes", count);
|
||||
if (LOG.isDebugEnabled()) {
|
||||
List<String> clsNames = new ArrayList<>(missingClasses);
|
||||
Collections.sort(clsNames);
|
||||
for (String cls : clsNames) {
|
||||
LOG.debug(" {}", cls);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
package jadx.core.clsp;
|
||||
|
||||
import jadx.api.DefaultJadxArgs;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.exceptions.DecodeException;
|
||||
import jadx.core.utils.files.InputFile;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
@@ -13,6 +8,11 @@ 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)
|
||||
*/
|
||||
@@ -30,20 +30,20 @@ public class ConvertToClsSet {
|
||||
}
|
||||
File output = new File(args[0]);
|
||||
|
||||
List<InputFile> inputFiles = new ArrayList<InputFile>(args.length - 1);
|
||||
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 {
|
||||
inputFiles.add(new InputFile(f));
|
||||
InputFile.addFilesFrom(f, inputFiles);
|
||||
}
|
||||
}
|
||||
for (InputFile inputFile : inputFiles) {
|
||||
LOG.info("Loaded: {}", inputFile.getFile());
|
||||
}
|
||||
|
||||
RootNode root = new RootNode(new DefaultJadxArgs());
|
||||
RootNode root = new RootNode(new JadxArgs());
|
||||
root.load(inputFiles);
|
||||
|
||||
ClsSet set = new ClsSet();
|
||||
@@ -53,8 +53,7 @@ public class ConvertToClsSet {
|
||||
LOG.info("done");
|
||||
}
|
||||
|
||||
private static void addFilesFromDirectory(File dir,
|
||||
List<InputFile> inputFiles) throws IOException, DecodeException {
|
||||
private static void addFilesFromDirectory(File dir, List<InputFile> inputFiles) {
|
||||
File[] files = dir.listFiles();
|
||||
if (files == null) {
|
||||
return;
|
||||
@@ -62,14 +61,13 @@ public class ConvertToClsSet {
|
||||
for (File file : files) {
|
||||
if (file.isDirectory()) {
|
||||
addFilesFromDirectory(file, inputFiles);
|
||||
}
|
||||
String fileName = file.getName();
|
||||
if (fileName.endsWith(".dex")
|
||||
|| fileName.endsWith(".jar")
|
||||
|| fileName.endsWith(".apk")) {
|
||||
inputFiles.add(new InputFile(file));
|
||||
} else {
|
||||
try {
|
||||
InputFile.addFilesFrom(file, inputFiles);
|
||||
} catch (Exception e) {
|
||||
LOG.warn("Skip file: {}, load error: {}", file, e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
package jadx.core.codegen;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import jadx.core.Consts;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.IAttributeNode;
|
||||
@@ -14,11 +19,6 @@ import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.utils.StringUtils;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
public class AnnotationGen {
|
||||
|
||||
private final ClassNode cls;
|
||||
@@ -130,11 +130,11 @@ public class AnnotationGen {
|
||||
return;
|
||||
}
|
||||
if (val instanceof String) {
|
||||
code.add(StringUtils.unescapeString((String) val));
|
||||
code.add(getStringUtils().unescapeString((String) val));
|
||||
} else if (val instanceof Integer) {
|
||||
code.add(TypeGen.formatInteger((Integer) val));
|
||||
} else if (val instanceof Character) {
|
||||
code.add(StringUtils.unescapeChar((Character) val));
|
||||
code.add(getStringUtils().unescapeChar((Character) val));
|
||||
} else if (val instanceof Boolean) {
|
||||
code.add(Boolean.TRUE.equals(val) ? "true" : "false");
|
||||
} else if (val instanceof Float) {
|
||||
@@ -172,4 +172,8 @@ public class AnnotationGen {
|
||||
throw new JadxRuntimeException("Can't decode value: " + val + " (" + val.getClass() + ")");
|
||||
}
|
||||
}
|
||||
|
||||
private StringUtils getStringUtils() {
|
||||
return cls.dex().root().getStringUtils();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +1,5 @@
|
||||
package jadx.core.codegen;
|
||||
|
||||
import jadx.api.IJadxArgs;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.AttrNode;
|
||||
import jadx.core.dex.attributes.nodes.EnumClassAttr;
|
||||
import jadx.core.dex.attributes.nodes.EnumClassAttr.EnumField;
|
||||
import jadx.core.dex.attributes.nodes.SourceFileAttr;
|
||||
import jadx.core.dex.info.AccessInfo;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.PrimitiveType;
|
||||
import jadx.core.dex.instructions.mods.ConstructorInsn;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.DexNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.parser.FieldValueAttr;
|
||||
import jadx.core.utils.ErrorsCounter;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.CodegenException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
@@ -31,42 +10,59 @@ import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.android.dx.rop.code.AccessFlags;
|
||||
|
||||
public class ClassGen {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ClassGen.class);
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.AttrNode;
|
||||
import jadx.core.dex.attributes.nodes.EnumClassAttr;
|
||||
import jadx.core.dex.attributes.nodes.EnumClassAttr.EnumField;
|
||||
import jadx.core.dex.attributes.nodes.JadxError;
|
||||
import jadx.core.dex.attributes.nodes.JadxWarn;
|
||||
import jadx.core.dex.attributes.nodes.LineAttrNode;
|
||||
import jadx.core.dex.attributes.nodes.SourceFileAttr;
|
||||
import jadx.core.dex.info.AccessInfo;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.PrimitiveType;
|
||||
import jadx.core.dex.instructions.mods.ConstructorInsn;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.DexNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.parser.FieldInitAttr;
|
||||
import jadx.core.dex.nodes.parser.FieldInitAttr.InitType;
|
||||
import jadx.core.utils.ErrorsCounter;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.CodegenException;
|
||||
|
||||
public static final Comparator<MethodNode> METHOD_LINE_COMPARATOR = new Comparator<MethodNode>() {
|
||||
@Override
|
||||
public int compare(MethodNode a, MethodNode b) {
|
||||
return Utils.compare(a.getSourceLine(), b.getSourceLine());
|
||||
}
|
||||
};
|
||||
public class ClassGen {
|
||||
|
||||
private final ClassNode cls;
|
||||
private final ClassGen parentGen;
|
||||
private final AnnotationGen annotationGen;
|
||||
private final boolean fallback;
|
||||
private final boolean useImports;
|
||||
private final boolean showInconsistentCode;
|
||||
|
||||
private final Set<ClassInfo> imports = new HashSet<ClassInfo>();
|
||||
private final Set<ClassInfo> imports = new HashSet<>();
|
||||
private int clsDeclLine;
|
||||
|
||||
public ClassGen(ClassNode cls, IJadxArgs jadxArgs) {
|
||||
this(cls, null, jadxArgs.isFallbackMode(), jadxArgs.isShowInconsistentCode());
|
||||
public ClassGen(ClassNode cls, JadxArgs jadxArgs) {
|
||||
this(cls, null, jadxArgs.isUseImports(), jadxArgs.isFallbackMode(), jadxArgs.isShowInconsistentCode());
|
||||
}
|
||||
|
||||
public ClassGen(ClassNode cls, ClassGen parentClsGen) {
|
||||
this(cls, parentClsGen, parentClsGen.fallback, parentClsGen.showInconsistentCode);
|
||||
this(cls, parentClsGen, parentClsGen.useImports, parentClsGen.fallback, parentClsGen.showInconsistentCode);
|
||||
}
|
||||
|
||||
public ClassGen(ClassNode cls, ClassGen parentClsGen, boolean fallback, boolean showBadCode) {
|
||||
public ClassGen(ClassNode cls, ClassGen parentClsGen, boolean useImports, boolean fallback, boolean showBadCode) {
|
||||
this.cls = cls;
|
||||
this.parentGen = parentClsGen;
|
||||
this.fallback = fallback;
|
||||
this.useImports = useImports;
|
||||
this.showInconsistentCode = showBadCode;
|
||||
|
||||
this.annotationGen = new AnnotationGen(cls, this);
|
||||
@@ -87,7 +83,7 @@ public class ClassGen {
|
||||
}
|
||||
int importsCount = imports.size();
|
||||
if (importsCount != 0) {
|
||||
List<String> sortImports = new ArrayList<String>(importsCount);
|
||||
List<String> sortImports = new ArrayList<>(importsCount);
|
||||
for (ClassInfo ic : imports) {
|
||||
sortImports.add(ic.getAlias().getFullName());
|
||||
}
|
||||
@@ -146,6 +142,7 @@ public class ClassGen {
|
||||
} else {
|
||||
clsCode.add("class ");
|
||||
}
|
||||
clsCode.attachDefinition(cls);
|
||||
clsCode.add(cls.getShortName());
|
||||
|
||||
addGenericMap(clsCode, cls.getGenericMap());
|
||||
@@ -177,7 +174,6 @@ public class ClassGen {
|
||||
clsCode.add(' ');
|
||||
}
|
||||
}
|
||||
clsCode.attachDefinition(cls);
|
||||
}
|
||||
|
||||
public boolean addGenericMap(CodeWriter code, Map<ArgType, List<ArgType>> gmap) {
|
||||
@@ -259,18 +255,22 @@ public class ClassGen {
|
||||
if (code.getLine() != clsDeclLine) {
|
||||
code.newLine();
|
||||
}
|
||||
int savedIndent = code.getIndent();
|
||||
try {
|
||||
addMethod(code, mth);
|
||||
} catch (Exception e) {
|
||||
String msg = ErrorsCounter.methodError(mth, "Method generation error", e);
|
||||
code.startLine("/* " + msg + CodeWriter.NL + Utils.getStackTrace(e) + " */");
|
||||
code.newLine().add("/*");
|
||||
code.newLine().add(ErrorsCounter.methodError(mth, "Method generation error", e));
|
||||
code.newLine().add(Utils.getStackTrace(e));
|
||||
code.newLine().add("*/");
|
||||
code.setIndent(savedIndent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static List<MethodNode> sortMethodsByLine(List<MethodNode> methods) {
|
||||
List<MethodNode> out = new ArrayList<MethodNode>(methods);
|
||||
Collections.sort(out, METHOD_LINE_COMPARATOR);
|
||||
List<MethodNode> out = new ArrayList<>(methods);
|
||||
out.sort(Comparator.comparingInt(LineAttrNode::getSourceLine));
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -296,12 +296,11 @@ public class ClassGen {
|
||||
}
|
||||
code.add(';');
|
||||
} else {
|
||||
insertDecompilationProblems(code, mth);
|
||||
boolean badCode = mth.contains(AFlag.INCONSISTENT_CODE);
|
||||
if (badCode) {
|
||||
code.startLine("/* JADX WARNING: inconsistent code. */");
|
||||
code.startLine("/* Code decompiled incorrectly, please refer to instructions dump. */");
|
||||
ErrorsCounter.methodError(mth, "Inconsistent code");
|
||||
if (showInconsistentCode) {
|
||||
code.startLine("/* Code decompiled incorrectly, please refer to instructions dump. */");
|
||||
mth.remove(AFlag.INCONSISTENT_CODE);
|
||||
badCode = false;
|
||||
}
|
||||
@@ -328,6 +327,26 @@ public class ClassGen {
|
||||
}
|
||||
}
|
||||
|
||||
private void insertDecompilationProblems(CodeWriter code, MethodNode mth) {
|
||||
List<JadxError> errors = mth.getAll(AType.JADX_ERROR);
|
||||
List<JadxWarn> warns = mth.getAll(AType.JADX_WARN);
|
||||
if (!errors.isEmpty()) {
|
||||
errors.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("*/");
|
||||
});
|
||||
}
|
||||
if (!warns.isEmpty()) {
|
||||
warns.forEach(warn -> code.startLine("/* JADX WARNING: ").add(warn.getWarn()).add(" */"));
|
||||
}
|
||||
}
|
||||
|
||||
private void addFields(CodeWriter code) throws CodegenException {
|
||||
addEnumFields(code);
|
||||
for (FieldNode f : cls.getFields()) {
|
||||
@@ -335,21 +354,30 @@ public class ClassGen {
|
||||
continue;
|
||||
}
|
||||
annotationGen.addForField(code, f);
|
||||
|
||||
if (f.getFieldInfo().isRenamed()) {
|
||||
code.startLine("/* renamed from: ").add(f.getName()).add(" */");
|
||||
}
|
||||
code.startLine(f.getAccessFlags().makeString());
|
||||
useType(code, f.getType());
|
||||
code.add(' ');
|
||||
code.attachDefinition(f);
|
||||
code.add(f.getAlias());
|
||||
FieldValueAttr fv = f.get(AType.FIELD_VALUE);
|
||||
FieldInitAttr fv = f.get(AType.FIELD_INIT);
|
||||
if (fv != null) {
|
||||
code.add(" = ");
|
||||
if (fv.getValue() == null) {
|
||||
code.add(TypeGen.literalToString(0, f.getType()));
|
||||
code.add(TypeGen.literalToString(0, f.getType(), cls));
|
||||
} else {
|
||||
annotationGen.encodeValue(code, fv.getValue());
|
||||
if (fv.getValueType() == InitType.CONST) {
|
||||
annotationGen.encodeValue(code, fv.getValue());
|
||||
} else if (fv.getValueType() == InitType.INSN) {
|
||||
InsnGen insnGen = makeInsnGen(fv.getInsnMth());
|
||||
addInsnBody(insnGen, code, fv.getInsn());
|
||||
}
|
||||
}
|
||||
}
|
||||
code.add(';');
|
||||
code.attachDefinition(f);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -374,8 +402,7 @@ public class ClassGen {
|
||||
ConstructorInsn constrInsn = f.getConstrInsn();
|
||||
if (constrInsn.getArgsCount() > f.getStartArg()) {
|
||||
if (igen == null) {
|
||||
MethodGen mthGen = new MethodGen(this, enumFields.getStaticMethod());
|
||||
igen = new InsnGen(mthGen, false);
|
||||
igen = makeInsnGen(enumFields.getStaticMethod());
|
||||
}
|
||||
MethodNode callMth = cls.dex().resolveMethod(constrInsn.getCallMth());
|
||||
igen.generateMethodArguments(code, constrInsn, f.getStartArg(), callMth);
|
||||
@@ -399,6 +426,19 @@ public class ClassGen {
|
||||
}
|
||||
}
|
||||
|
||||
private InsnGen makeInsnGen(MethodNode mth) {
|
||||
MethodGen mthGen = new MethodGen(this, mth);
|
||||
return new InsnGen(mthGen, false);
|
||||
}
|
||||
|
||||
private void addInsnBody(InsnGen insnGen, CodeWriter code, InsnNode insn) {
|
||||
try {
|
||||
insnGen.makeInsn(insn, code, InsnGen.Flags.BODY_ONLY_NOWRAP);
|
||||
} catch (Exception e) {
|
||||
ErrorsCounter.classError(cls, "Failed to generate init code", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void useType(CodeWriter code, ArgType type) {
|
||||
PrimitiveType stype = type.getPrimitiveType();
|
||||
if (stype == null) {
|
||||
@@ -418,7 +458,7 @@ public class ClassGen {
|
||||
}
|
||||
|
||||
public void useClass(CodeWriter code, ArgType type) {
|
||||
useClass(code, ClassInfo.extCls(cls.dex(), type));
|
||||
useClass(code, ClassInfo.extCls(cls.root(), type));
|
||||
ArgType[] generics = type.getGenericTypes();
|
||||
if (generics != null) {
|
||||
code.add('<');
|
||||
@@ -455,7 +495,7 @@ public class ClassGen {
|
||||
|
||||
private String useClassInternal(ClassInfo useCls, ClassInfo extClsInfo) {
|
||||
String fullName = extClsInfo.getFullName();
|
||||
if (fallback) {
|
||||
if (fallback || !useImports) {
|
||||
return fullName;
|
||||
}
|
||||
String shortName = extClsInfo.getShortName();
|
||||
@@ -480,6 +520,10 @@ public class ClassGen {
|
||||
if (searchCollision(cls.dex(), useCls, extClsInfo)) {
|
||||
return fullName;
|
||||
}
|
||||
// ignore classes from default package
|
||||
if (extClsInfo.isDefaultPackage()) {
|
||||
return shortName;
|
||||
}
|
||||
if (extClsInfo.getPackage().equals(useCls.getPackage())) {
|
||||
fullName = extClsInfo.getNameWithoutPackage();
|
||||
}
|
||||
@@ -562,7 +606,7 @@ public class ClassGen {
|
||||
private void insertRenameInfo(CodeWriter code, ClassNode cls) {
|
||||
ClassInfo classInfo = cls.getClassInfo();
|
||||
if (classInfo.isRenamed()) {
|
||||
code.startLine("/* renamed from: ").add(classInfo.getFullName()).add(" */");
|
||||
code.startLine("/* renamed from: ").add(classInfo.getType().getObject()).add(" */");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,25 +1,15 @@
|
||||
package jadx.core.codegen;
|
||||
|
||||
import jadx.api.IJadxArgs;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.visitors.AbstractVisitor;
|
||||
import jadx.core.utils.exceptions.CodegenException;
|
||||
|
||||
public class CodeGen extends AbstractVisitor {
|
||||
public class CodeGen {
|
||||
|
||||
private final IJadxArgs args;
|
||||
|
||||
public CodeGen(IJadxArgs args) {
|
||||
this.args = args;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visit(ClassNode cls) throws CodegenException {
|
||||
ClassGen clsGen = new ClassGen(cls, args);
|
||||
ClassGen clsGen = new ClassGen(cls, cls.root().getArgs());
|
||||
CodeWriter clsCode = clsGen.makeClass();
|
||||
clsCode.finish();
|
||||
cls.setCode(clsCode);
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
package jadx.core.codegen;
|
||||
|
||||
import jadx.api.CodePosition;
|
||||
import jadx.core.dex.attributes.nodes.LineAttrNode;
|
||||
import jadx.core.utils.files.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Collections;
|
||||
@@ -12,28 +8,36 @@ import java.util.Iterator;
|
||||
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);
|
||||
private static final int MAX_FILENAME_LENGTH = 128;
|
||||
|
||||
public static final String NL = System.getProperty("line.separator");
|
||||
public static final String INDENT = " ";
|
||||
public static final String INDENT_STR = " ";
|
||||
|
||||
private static final boolean ADD_LINE_NUMBERS = false;
|
||||
|
||||
private static final String[] INDENT_CACHE = {
|
||||
"",
|
||||
INDENT,
|
||||
INDENT + INDENT,
|
||||
INDENT + INDENT + INDENT,
|
||||
INDENT + INDENT + INDENT + INDENT,
|
||||
INDENT + INDENT + INDENT + INDENT + INDENT,
|
||||
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 final StringBuilder buf = new StringBuilder();
|
||||
private StringBuilder buf = new StringBuilder();
|
||||
@Nullable
|
||||
private String code;
|
||||
private String indentStr;
|
||||
private int indent;
|
||||
|
||||
@@ -90,6 +94,15 @@ public class CodeWriter {
|
||||
return this;
|
||||
}
|
||||
|
||||
public CodeWriter addMultiLine(String str) {
|
||||
buf.append(str);
|
||||
if (str.contains(NL)) {
|
||||
line += StringUtils.countMatches(str, NL);
|
||||
offset = 0;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public CodeWriter add(String str) {
|
||||
buf.append(str);
|
||||
offset += str.length();
|
||||
@@ -113,7 +126,7 @@ public class CodeWriter {
|
||||
}
|
||||
line += code.line;
|
||||
offset = code.offset;
|
||||
buf.append(code);
|
||||
buf.append(code.buf);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -123,7 +136,7 @@ public class CodeWriter {
|
||||
}
|
||||
|
||||
public CodeWriter addIndent() {
|
||||
add(INDENT);
|
||||
add(INDENT_STR);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -144,9 +157,9 @@ public class CodeWriter {
|
||||
if (curIndent < INDENT_CACHE.length) {
|
||||
this.indentStr = INDENT_CACHE[curIndent];
|
||||
} else {
|
||||
StringBuilder s = new StringBuilder(curIndent * INDENT.length());
|
||||
StringBuilder s = new StringBuilder(curIndent * INDENT_STR.length());
|
||||
for (int i = 0; i < curIndent; i++) {
|
||||
s.append(INDENT);
|
||||
s.append(INDENT_STR);
|
||||
}
|
||||
this.indentStr = s.toString();
|
||||
}
|
||||
@@ -178,6 +191,11 @@ public class CodeWriter {
|
||||
return indent;
|
||||
}
|
||||
|
||||
public void setIndent(int indent) {
|
||||
this.indent = indent;
|
||||
updateIndent();
|
||||
}
|
||||
|
||||
public int getLine() {
|
||||
return line;
|
||||
}
|
||||
@@ -194,17 +212,18 @@ public class CodeWriter {
|
||||
}
|
||||
}
|
||||
|
||||
public Object attachDefinition(LineAttrNode obj) {
|
||||
return attachAnnotation(new DefinitionWrapper(obj), new CodePosition(line, offset));
|
||||
public void attachDefinition(LineAttrNode obj) {
|
||||
attachAnnotation(obj);
|
||||
attachAnnotation(new DefinitionWrapper(obj), new CodePosition(line, offset));
|
||||
}
|
||||
|
||||
public Object attachAnnotation(Object obj) {
|
||||
return attachAnnotation(obj, new CodePosition(line, offset + 1));
|
||||
public void attachAnnotation(Object obj) {
|
||||
attachAnnotation(obj, new CodePosition(line, offset + 1));
|
||||
}
|
||||
|
||||
private Object attachAnnotation(Object obj, CodePosition pos) {
|
||||
if (annotations.isEmpty()) {
|
||||
annotations = new HashMap<CodePosition, Object>();
|
||||
annotations = new HashMap<>();
|
||||
}
|
||||
return annotations.put(pos, obj);
|
||||
}
|
||||
@@ -222,7 +241,7 @@ public class CodeWriter {
|
||||
|
||||
private void attachSourceLine(int decompiledLine, int sourceLine) {
|
||||
if (lineMap.isEmpty()) {
|
||||
lineMap = new TreeMap<Integer, Integer>();
|
||||
lineMap = new TreeMap<>();
|
||||
}
|
||||
lineMap.put(decompiledLine, sourceLine);
|
||||
}
|
||||
@@ -232,7 +251,11 @@ public class CodeWriter {
|
||||
}
|
||||
|
||||
public void finish() {
|
||||
removeFirstEmptyLine();
|
||||
buf.trimToSize();
|
||||
code = buf.toString();
|
||||
buf = null;
|
||||
|
||||
Iterator<Map.Entry<CodePosition, Object>> it = annotations.entrySet().iterator();
|
||||
while (it.hasNext()) {
|
||||
Map.Entry<CodePosition, Object> entry = it.next();
|
||||
@@ -245,81 +268,49 @@ public class CodeWriter {
|
||||
}
|
||||
}
|
||||
|
||||
private static String removeFirstEmptyLine(String str) {
|
||||
if (str.startsWith(NL)) {
|
||||
return str.substring(NL.length());
|
||||
private void removeFirstEmptyLine() {
|
||||
int len = NL.length();
|
||||
if (buf.substring(0, len).equals(NL)) {
|
||||
buf.delete(0, len);
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
public int length() {
|
||||
public int bufLength() {
|
||||
return buf.length();
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return buf.length() == 0;
|
||||
}
|
||||
|
||||
public boolean notEmpty() {
|
||||
return buf.length() != 0;
|
||||
public String getCodeStr() {
|
||||
return code;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return buf.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) {
|
||||
String name = file.getName();
|
||||
if (name.length() > MAX_FILENAME_LENGTH) {
|
||||
int dotIndex = name.indexOf('.');
|
||||
int cutAt = MAX_FILENAME_LENGTH - name.length() + dotIndex - 1;
|
||||
if (cutAt <= 0) {
|
||||
name = name.substring(0, MAX_FILENAME_LENGTH - 1);
|
||||
} else {
|
||||
name = name.substring(0, cutAt) + name.substring(dotIndex);
|
||||
}
|
||||
file = new File(file.getParentFile(), name);
|
||||
if (code == null) {
|
||||
finish();
|
||||
}
|
||||
|
||||
PrintWriter out = null;
|
||||
try {
|
||||
FileUtils.makeDirsForFile(file);
|
||||
out = new PrintWriter(file, "UTF-8");
|
||||
String code = buf.toString();
|
||||
code = removeFirstEmptyLine(code);
|
||||
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);
|
||||
} finally {
|
||||
if (out != null) {
|
||||
out.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return buf.toString().hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (!(o instanceof CodeWriter)) {
|
||||
return false;
|
||||
}
|
||||
CodeWriter that = (CodeWriter) o;
|
||||
return buf.toString().equals(that.buf.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
package jadx.core.codegen;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Queue;
|
||||
|
||||
import jadx.core.dex.instructions.ArithNode;
|
||||
import jadx.core.dex.instructions.IfOp;
|
||||
import jadx.core.dex.instructions.InsnType;
|
||||
@@ -15,18 +19,10 @@ import jadx.core.utils.ErrorsCounter;
|
||||
import jadx.core.utils.exceptions.CodegenException;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Queue;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class ConditionGen extends InsnGen {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ConditionGen.class);
|
||||
|
||||
private static class CondStack {
|
||||
private final Queue<IfCondition> stack = new LinkedList<IfCondition>();
|
||||
private final Queue<IfCondition> stack = new LinkedList<>();
|
||||
|
||||
public Queue<IfCondition> getStack() {
|
||||
return stack;
|
||||
@@ -126,7 +122,7 @@ public class ConditionGen extends InsnGen {
|
||||
wrap(code, firstArg);
|
||||
return;
|
||||
}
|
||||
LOG.warn(ErrorsCounter.formatErrorMsg(mth, "Unsupported boolean condition " + op.getSymbol()));
|
||||
ErrorsCounter.methodWarn(mth, "Unsupported boolean condition " + op.getSymbol());
|
||||
}
|
||||
|
||||
addArg(code, firstArg, isArgWrapNeeded(firstArg));
|
||||
@@ -179,6 +175,9 @@ public class ConditionGen extends InsnGen {
|
||||
case DIV:
|
||||
case REM:
|
||||
return false;
|
||||
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
switch (insnType) {
|
||||
@@ -189,10 +188,10 @@ public class ConditionGen extends InsnGen {
|
||||
case CONST:
|
||||
case ARRAY_LENGTH:
|
||||
return false;
|
||||
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
package jadx.core.codegen;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
|
||||
@@ -36,23 +47,12 @@ import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.ErrorsCounter;
|
||||
import jadx.core.utils.RegionUtils;
|
||||
import jadx.core.utils.StringUtils;
|
||||
import jadx.core.utils.exceptions.CodegenException;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import static jadx.core.utils.android.AndroidResourcesUtils.handleAppResField;
|
||||
|
||||
public class InsnGen {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(InsnGen.class);
|
||||
@@ -80,9 +80,9 @@ public class InsnGen {
|
||||
}
|
||||
|
||||
public void addArgDot(CodeWriter code, InsnArg arg) throws CodegenException {
|
||||
int len = code.length();
|
||||
int len = code.bufLength();
|
||||
addArg(code, arg, true);
|
||||
if (len != code.length()) {
|
||||
if (len != code.bufLength()) {
|
||||
code.add('.');
|
||||
}
|
||||
}
|
||||
@@ -123,41 +123,45 @@ public class InsnGen {
|
||||
}
|
||||
|
||||
public void declareVar(CodeWriter code, RegisterArg arg) {
|
||||
if (arg.getSVar().contains(AFlag.FINAL)) {
|
||||
code.add("final ");
|
||||
}
|
||||
useType(code, arg.getType());
|
||||
code.add(' ');
|
||||
code.add(mgen.getNameGen().assignArg(arg));
|
||||
}
|
||||
|
||||
private static String lit(LiteralArg arg) {
|
||||
return TypeGen.literalToString(arg.getLiteral(), arg.getType());
|
||||
private String lit(LiteralArg arg) {
|
||||
return TypeGen.literalToString(arg.getLiteral(), arg.getType(), mth);
|
||||
}
|
||||
|
||||
private void instanceField(CodeWriter code, FieldInfo field, InsnArg arg) throws CodegenException {
|
||||
ClassNode pCls = mth.getParentClass();
|
||||
FieldNode fieldNode = pCls.searchField(field);
|
||||
while (fieldNode == null
|
||||
&& pCls.getParentClass() != pCls
|
||||
&& pCls.getParentClass() != null) {
|
||||
pCls = pCls.getParentClass();
|
||||
fieldNode = pCls.searchField(field);
|
||||
}
|
||||
FieldNode fieldNode = pCls.dex().root().deepResolveField(field);
|
||||
if (fieldNode != null) {
|
||||
FieldReplaceAttr replace = fieldNode.get(AType.FIELD_REPLACE);
|
||||
if (replace != null) {
|
||||
FieldInfo info = replace.getFieldInfo();
|
||||
if (replace.isOuterClass()) {
|
||||
useClass(code, info.getDeclClass());
|
||||
code.add(".this");
|
||||
switch (replace.getReplaceType()) {
|
||||
case CLASS_INSTANCE:
|
||||
useClass(code, replace.getClsRef());
|
||||
code.add(".this");
|
||||
break;
|
||||
case VAR:
|
||||
addArg(code, replace.getVarRef());
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
addArgDot(code, arg);
|
||||
fieldNode = mth.dex().resolveField(field);
|
||||
if (fieldNode != null) {
|
||||
code.attachAnnotation(fieldNode);
|
||||
}
|
||||
code.add(field.getAlias());
|
||||
if (fieldNode == null) {
|
||||
code.add(field.getAlias());
|
||||
} else {
|
||||
code.add(fieldNode.getAlias());
|
||||
}
|
||||
}
|
||||
|
||||
public static void makeStaticFieldAccess(CodeWriter code, FieldInfo field, ClassGen clsGen) {
|
||||
@@ -165,21 +169,20 @@ public class InsnGen {
|
||||
boolean fieldFromThisClass = clsGen.getClassNode().getClassInfo().equals(declClass);
|
||||
if (!fieldFromThisClass) {
|
||||
// Android specific resources class handler
|
||||
ClassInfo parentClass = declClass.getParentClass();
|
||||
if (parentClass != null && parentClass.getShortName().equals("R")) {
|
||||
clsGen.useClass(code, parentClass);
|
||||
code.add('.');
|
||||
code.add(declClass.getAlias().getShortName());
|
||||
} else {
|
||||
if (!handleAppResField(code, clsGen, declClass)) {
|
||||
clsGen.useClass(code, declClass);
|
||||
}
|
||||
code.add('.');
|
||||
}
|
||||
FieldNode fieldNode = clsGen.getClassNode().dex().resolveField(field);
|
||||
FieldNode fieldNode = clsGen.getClassNode().dex().root().deepResolveField(field);
|
||||
if (fieldNode != null) {
|
||||
code.attachAnnotation(fieldNode);
|
||||
}
|
||||
code.add(field.getAlias());
|
||||
if (fieldNode == null) {
|
||||
code.add(field.getAlias());
|
||||
} else {
|
||||
code.add(fieldNode.getAlias());
|
||||
}
|
||||
}
|
||||
|
||||
protected void staticField(CodeWriter code, FieldInfo field) {
|
||||
@@ -231,7 +234,7 @@ public class InsnGen {
|
||||
switch (insn.getType()) {
|
||||
case CONST_STR:
|
||||
String str = ((ConstStringNode) insn).getString();
|
||||
code.add(StringUtils.unescapeString(str));
|
||||
code.add(mth.dex().root().getStringUtils().unescapeString(str));
|
||||
break;
|
||||
|
||||
case CONST_CLASS:
|
||||
@@ -269,18 +272,13 @@ public class InsnGen {
|
||||
makeArith((ArithNode) insn, code, state);
|
||||
break;
|
||||
|
||||
case NEG: {
|
||||
boolean wrap = state.contains(Flags.BODY_ONLY);
|
||||
if (wrap) {
|
||||
code.add('(');
|
||||
}
|
||||
code.add('-');
|
||||
addArg(code, insn.getArg(0));
|
||||
if (wrap) {
|
||||
code.add(')');
|
||||
}
|
||||
case NEG:
|
||||
oneArgInsn(code, insn, state, '-');
|
||||
break;
|
||||
|
||||
case NOT:
|
||||
oneArgInsn(code, insn, state, '~');
|
||||
break;
|
||||
}
|
||||
|
||||
case RETURN:
|
||||
if (insn.getArgsCount() != 0) {
|
||||
@@ -445,7 +443,7 @@ public class InsnGen {
|
||||
|
||||
/* fallback mode instructions */
|
||||
case IF:
|
||||
assert isFallback() : "if insn in not fallback mode";
|
||||
fallbackOnlyInsn(insn);
|
||||
IfNode ifInsn = (IfNode) insn;
|
||||
code.add("if (");
|
||||
addArg(code, insn.getArg(0));
|
||||
@@ -456,17 +454,17 @@ public class InsnGen {
|
||||
break;
|
||||
|
||||
case GOTO:
|
||||
assert isFallback();
|
||||
fallbackOnlyInsn(insn);
|
||||
code.add("goto ").add(MethodGen.getLabelName(((GotoNode) insn).getTarget()));
|
||||
break;
|
||||
|
||||
case MOVE_EXCEPTION:
|
||||
assert isFallback();
|
||||
fallbackOnlyInsn(insn);
|
||||
code.add("move-exception");
|
||||
break;
|
||||
|
||||
case SWITCH:
|
||||
assert isFallback();
|
||||
fallbackOnlyInsn(insn);
|
||||
SwitchNode sw = (SwitchNode) insn;
|
||||
code.add("switch(");
|
||||
addArg(code, insn.getArg(0));
|
||||
@@ -484,7 +482,7 @@ public class InsnGen {
|
||||
break;
|
||||
|
||||
case FILL_ARRAY:
|
||||
assert isFallback();
|
||||
fallbackOnlyInsn(insn);
|
||||
FillArrayNode arrayNode = (FillArrayNode) insn;
|
||||
Object data = arrayNode.getData();
|
||||
String arrStr;
|
||||
@@ -504,8 +502,19 @@ public class InsnGen {
|
||||
|
||||
case NEW_INSTANCE:
|
||||
// only fallback - make new instance in constructor invoke
|
||||
assert isFallback();
|
||||
code.add("new " + insn.getResult().getType());
|
||||
fallbackOnlyInsn(insn);
|
||||
code.add("new ").add(insn.getResult().getType().toString());
|
||||
break;
|
||||
|
||||
case PHI:
|
||||
case MERGE:
|
||||
fallbackOnlyInsn(insn);
|
||||
code.add(insn.getType().toString()).add("(");
|
||||
for (InsnArg insnArg : insn.getArguments()) {
|
||||
addArg(code, insnArg);
|
||||
code.add(' ');
|
||||
}
|
||||
code.add(")");
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -513,6 +522,24 @@ public class InsnGen {
|
||||
}
|
||||
}
|
||||
|
||||
private void oneArgInsn(CodeWriter code, InsnNode insn, Set<Flags> state, char op) throws CodegenException {
|
||||
boolean wrap = state.contains(Flags.BODY_ONLY);
|
||||
if (wrap) {
|
||||
code.add('(');
|
||||
}
|
||||
code.add(op);
|
||||
addArg(code, insn.getArg(0));
|
||||
if (wrap) {
|
||||
code.add(')');
|
||||
}
|
||||
}
|
||||
|
||||
private void fallbackOnlyInsn(InsnNode insn) throws CodegenException {
|
||||
if (!fallback) {
|
||||
throw new CodegenException(insn.getType() + " can be used only in fallback mode");
|
||||
}
|
||||
}
|
||||
|
||||
private void filledNewArray(FilledNewArrayNode insn, CodeWriter code) throws CodegenException {
|
||||
code.add("new ");
|
||||
useType(code, insn.getArrayType());
|
||||
@@ -531,30 +558,7 @@ public class InsnGen {
|
||||
throws CodegenException {
|
||||
ClassNode cls = mth.dex().resolveClass(insn.getClassType());
|
||||
if (cls != null && cls.contains(AFlag.ANONYMOUS_CLASS) && !fallback) {
|
||||
// anonymous class construction
|
||||
ArgType parent;
|
||||
if (cls.getInterfaces().size() == 1) {
|
||||
parent = cls.getInterfaces().get(0);
|
||||
} else {
|
||||
parent = cls.getSuperClass();
|
||||
}
|
||||
cls.add(AFlag.DONT_GENERATE);
|
||||
MethodNode defCtr = cls.getDefaultConstructor();
|
||||
if (defCtr != null) {
|
||||
if (RegionUtils.notEmpty(defCtr.getRegion())) {
|
||||
defCtr.add(AFlag.ANONYMOUS_CONSTRUCTOR);
|
||||
} else {
|
||||
defCtr.add(AFlag.DONT_GENERATE);
|
||||
}
|
||||
}
|
||||
code.add("new ");
|
||||
if (parent == null) {
|
||||
code.add("Object");
|
||||
} else {
|
||||
useClass(code, parent);
|
||||
}
|
||||
code.add("() ");
|
||||
new ClassGen(cls, mgen.getClassGen().getParentGen()).addClassBody(code);
|
||||
inlineAnonymousConstr(code, cls, insn);
|
||||
return;
|
||||
}
|
||||
if (insn.isSelf()) {
|
||||
@@ -568,14 +572,49 @@ public class InsnGen {
|
||||
code.add("new ");
|
||||
useClass(code, insn.getClassType());
|
||||
}
|
||||
generateMethodArguments(code, insn, 0, mth.dex().resolveMethod(insn.getCallMth()));
|
||||
MethodNode callMth = mth.dex().resolveMethod(insn.getCallMth());
|
||||
generateMethodArguments(code, insn, 0, callMth);
|
||||
}
|
||||
|
||||
private void inlineAnonymousConstr(CodeWriter code, ClassNode cls, ConstructorInsn insn) throws CodegenException {
|
||||
// anonymous class construction
|
||||
if (cls.contains(AFlag.DONT_GENERATE)) {
|
||||
code.add("/* anonymous class already generated */");
|
||||
ErrorsCounter.methodWarn(mth, "Anonymous class already generated: " + cls);
|
||||
return;
|
||||
}
|
||||
ArgType parent;
|
||||
if (cls.getInterfaces().size() == 1) {
|
||||
parent = cls.getInterfaces().get(0);
|
||||
} else {
|
||||
parent = cls.getSuperClass();
|
||||
}
|
||||
cls.add(AFlag.DONT_GENERATE);
|
||||
MethodNode defCtr = cls.getDefaultConstructor();
|
||||
if (defCtr != null) {
|
||||
if (RegionUtils.notEmpty(defCtr.getRegion())) {
|
||||
defCtr.add(AFlag.ANONYMOUS_CONSTRUCTOR);
|
||||
} else {
|
||||
defCtr.add(AFlag.DONT_GENERATE);
|
||||
}
|
||||
}
|
||||
code.add("new ");
|
||||
if (parent == null) {
|
||||
code.add("Object");
|
||||
} else {
|
||||
useClass(code, parent);
|
||||
}
|
||||
MethodNode callMth = mth.dex().resolveMethod(insn.getCallMth());
|
||||
generateMethodArguments(code, insn, 0, callMth);
|
||||
code.add(' ');
|
||||
new ClassGen(cls, mgen.getClassGen().getParentGen()).addClassBody(code);
|
||||
}
|
||||
|
||||
private void makeInvoke(InvokeNode insn, CodeWriter code) throws CodegenException {
|
||||
MethodInfo callMth = insn.getCallMth();
|
||||
|
||||
// inline method
|
||||
MethodNode callMthNode = mth.dex().deepResolveMethod(callMth);
|
||||
MethodNode callMthNode = mth.root().deepResolveMethod(callMth);
|
||||
if (callMthNode != null) {
|
||||
if (inlineMethod(callMthNode, insn, code)) {
|
||||
return;
|
||||
@@ -627,23 +666,43 @@ public class InsnGen {
|
||||
}
|
||||
int argsCount = insn.getArgsCount();
|
||||
code.add('(');
|
||||
boolean firstArg = true;
|
||||
if (k < argsCount) {
|
||||
boolean overloaded = callMth != null && callMth.isArgsOverload();
|
||||
for (int i = k; i < argsCount; i++) {
|
||||
InsnArg arg = insn.getArg(i);
|
||||
if (arg.contains(AFlag.SKIP_ARG)) {
|
||||
continue;
|
||||
}
|
||||
RegisterArg callArg = getCallMthArg(callMth, i - startArgNum);
|
||||
if (callArg != null && callArg.contains(AFlag.SKIP_ARG)) {
|
||||
continue;
|
||||
}
|
||||
if (!firstArg) {
|
||||
code.add(", ");
|
||||
}
|
||||
boolean cast = overloaded && processOverloadedArg(code, callMth, arg, i - startArgNum);
|
||||
if (!cast && i == argsCount - 1 && processVarArg(code, callMth, arg)) {
|
||||
continue;
|
||||
}
|
||||
addArg(code, arg, false);
|
||||
if (i < argsCount - 1) {
|
||||
code.add(", ");
|
||||
}
|
||||
firstArg = false;
|
||||
}
|
||||
}
|
||||
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.
|
||||
*/
|
||||
@@ -701,9 +760,9 @@ public class InsnGen {
|
||||
regs[callArg.getRegNum()] = arg;
|
||||
}
|
||||
// replace args
|
||||
List<RegisterArg> inlArgs = new ArrayList<RegisterArg>();
|
||||
inl.getRegisterArgs(inlArgs);
|
||||
Map<RegisterArg, InsnArg> toRevert = new HashMap<RegisterArg, InsnArg>();
|
||||
InsnNode inlCopy = inl.copy();
|
||||
List<RegisterArg> inlArgs = new ArrayList<>();
|
||||
inlCopy.getRegisterArgs(inlArgs);
|
||||
for (RegisterArg r : inlArgs) {
|
||||
int regNum = r.getRegNum();
|
||||
if (regNum >= regs.length) {
|
||||
@@ -713,16 +772,11 @@ public class InsnGen {
|
||||
if (repl == null) {
|
||||
LOG.warn("Not passed register {} in method call: {} from {}", r, callMthNode, mth);
|
||||
} else {
|
||||
inl.replaceArg(r, repl);
|
||||
toRevert.put(r, repl);
|
||||
inlCopy.replaceArg(r, repl);
|
||||
}
|
||||
}
|
||||
}
|
||||
makeInsn(inl, code, Flags.BODY_ONLY);
|
||||
// revert changes in 'MethodInlineAttr'
|
||||
for (Map.Entry<RegisterArg, InsnArg> e : toRevert.entrySet()) {
|
||||
inl.replaceArg(e.getValue(), e.getKey());
|
||||
}
|
||||
makeInsn(inlCopy, code, Flags.BODY_ONLY);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,13 +1,20 @@
|
||||
package jadx.core.codegen;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import com.android.dx.rop.code.AccessFlags;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.annotations.MethodParameters;
|
||||
import jadx.core.dex.attributes.nodes.JadxErrorAttr;
|
||||
import jadx.core.dex.info.AccessInfo;
|
||||
import jadx.core.dex.instructions.InsnType;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.instructions.args.SSAVar;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.trycatch.CatchAttr;
|
||||
@@ -15,18 +22,9 @@ import jadx.core.dex.visitors.DepthTraversal;
|
||||
import jadx.core.dex.visitors.FallbackModeVisitor;
|
||||
import jadx.core.utils.ErrorsCounter;
|
||||
import jadx.core.utils.InsnUtils;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.CodegenException;
|
||||
import jadx.core.utils.exceptions.DecodeException;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.android.dx.rop.code.AccessFlags;
|
||||
|
||||
public class MethodGen {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MethodGen.class);
|
||||
|
||||
@@ -56,8 +54,8 @@ public class MethodGen {
|
||||
|
||||
public boolean addDefinition(CodeWriter code) {
|
||||
if (mth.getMethodInfo().isClassInit()) {
|
||||
code.startLine("static");
|
||||
code.attachDefinition(mth);
|
||||
code.startLine("static");
|
||||
return true;
|
||||
}
|
||||
if (mth.contains(AFlag.ANONYMOUS_CONSTRUCTOR)) {
|
||||
@@ -79,6 +77,10 @@ public class MethodGen {
|
||||
if (clsAccFlags.isAnnotation()) {
|
||||
ai = ai.remove(AccessFlags.ACC_PUBLIC);
|
||||
}
|
||||
|
||||
if (mth.getMethodInfo().isRenamed()) {
|
||||
code.startLine("/* renamed from: ").add(mth.getName()).add(" */");
|
||||
}
|
||||
code.startLineWithNum(mth.getSourceLine());
|
||||
code.add(ai.makeString());
|
||||
|
||||
@@ -86,10 +88,12 @@ public class MethodGen {
|
||||
code.add(' ');
|
||||
}
|
||||
if (mth.getAccessFlags().isConstructor()) {
|
||||
code.attachDefinition(mth);
|
||||
code.add(classGen.getClassNode().getShortName()); // constructor
|
||||
} else {
|
||||
classGen.useType(code, mth.getReturnType());
|
||||
code.add(' ');
|
||||
code.attachDefinition(mth);
|
||||
code.add(mth.getAlias());
|
||||
}
|
||||
code.add('(');
|
||||
@@ -102,7 +106,7 @@ public class MethodGen {
|
||||
} else if (args.size() > 2) {
|
||||
args = args.subList(2, args.size());
|
||||
} else {
|
||||
LOG.warn(ErrorsCounter.formatErrorMsg(mth,
|
||||
LOG.warn(ErrorsCounter.formatMsg(mth,
|
||||
"Incorrect number of args for enum constructor: " + args.size()
|
||||
+ " (expected >= 2)"
|
||||
));
|
||||
@@ -112,7 +116,6 @@ public class MethodGen {
|
||||
code.add(')');
|
||||
|
||||
annotationGen.addThrows(mth, code);
|
||||
code.attachDefinition(mth);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -126,6 +129,10 @@ public class MethodGen {
|
||||
if (paramsAnnotation != null) {
|
||||
annotationGen.addForParameter(argsCode, paramsAnnotation, i);
|
||||
}
|
||||
SSAVar argSVar = arg.getSVar();
|
||||
if (argSVar != null && argSVar.contains(AFlag.FINAL)) {
|
||||
argsCode.add("final ");
|
||||
}
|
||||
if (!it.hasNext() && mth.getAccessFlags().isVarArgs()) {
|
||||
// change last array argument to varargs
|
||||
ArgType type = arg.getType();
|
||||
@@ -134,7 +141,7 @@ public class MethodGen {
|
||||
classGen.useType(argsCode, elType);
|
||||
argsCode.add("...");
|
||||
} else {
|
||||
LOG.warn(ErrorsCounter.formatErrorMsg(mth, "Last argument in varargs method not array"));
|
||||
LOG.warn(ErrorsCounter.formatMsg(mth, "Last argument in varargs method not array"));
|
||||
classGen.useType(argsCode, arg.getType());
|
||||
}
|
||||
} else {
|
||||
@@ -154,17 +161,6 @@ public class MethodGen {
|
||||
if (mth.contains(AType.JADX_ERROR)
|
||||
|| mth.contains(AFlag.INCONSISTENT_CODE)
|
||||
|| mth.getRegion() == null) {
|
||||
JadxErrorAttr err = mth.get(AType.JADX_ERROR);
|
||||
if (err != null) {
|
||||
code.startLine("/* JADX: method processing error */");
|
||||
Throwable cause = err.getCause();
|
||||
if (cause != null) {
|
||||
code.newLine();
|
||||
code.add("/*");
|
||||
code.startLine("Error: ").add(Utils.getStackTrace(cause));
|
||||
code.add("*/");
|
||||
}
|
||||
}
|
||||
code.startLine("/*");
|
||||
addFallbackMethodCode(code);
|
||||
code.startLine("*/");
|
||||
@@ -180,19 +176,14 @@ public class MethodGen {
|
||||
|
||||
public void addFallbackMethodCode(CodeWriter code) {
|
||||
if (mth.getInstructions() == null) {
|
||||
JadxErrorAttr errorAttr = mth.get(AType.JADX_ERROR);
|
||||
if (errorAttr == null
|
||||
|| errorAttr.getCause() == null
|
||||
|| !errorAttr.getCause().getClass().equals(DecodeException.class)) {
|
||||
// load original instructions
|
||||
try {
|
||||
mth.load();
|
||||
DepthTraversal.visit(new FallbackModeVisitor(), mth);
|
||||
} catch (DecodeException e) {
|
||||
LOG.error("Error reload instructions in fallback mode:", e);
|
||||
code.startLine("// Can't load method instructions: " + e.getMessage());
|
||||
return;
|
||||
}
|
||||
// load original instructions
|
||||
try {
|
||||
mth.load();
|
||||
DepthTraversal.visit(new FallbackModeVisitor(), mth);
|
||||
} catch (DecodeException e) {
|
||||
LOG.error("Error reload instructions in fallback mode:", e);
|
||||
code.startLine("// Can't load method instructions: " + e.getMessage());
|
||||
return;
|
||||
}
|
||||
}
|
||||
InsnNode[] insnArr = mth.getInstructions();
|
||||
@@ -235,12 +226,11 @@ public class MethodGen {
|
||||
* Return fallback variant of method codegen
|
||||
*/
|
||||
public static MethodGen getFallbackMethodGen(MethodNode mth) {
|
||||
ClassGen clsGen = new ClassGen(mth.getParentClass(), null, true, true);
|
||||
ClassGen clsGen = new ClassGen(mth.getParentClass(), null, true, true, true);
|
||||
return new MethodGen(clsGen, mth);
|
||||
}
|
||||
|
||||
public static String getLabelName(int offset) {
|
||||
return "L_" + InsnUtils.formatOffset(offset);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
package jadx.core.codegen;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import jadx.core.Consts;
|
||||
import jadx.core.deobf.NameMapper;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
@@ -17,21 +23,16 @@ import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.utils.StringUtils;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class NameGen {
|
||||
|
||||
private static final Map<String, String> OBJ_ALIAS;
|
||||
|
||||
private final Set<String> varNames = new HashSet<String>();
|
||||
private final Set<String> varNames = new LinkedHashSet<>();
|
||||
private final MethodNode mth;
|
||||
private final boolean fallback;
|
||||
|
||||
static {
|
||||
OBJ_ALIAS = new HashMap<String, String>();
|
||||
OBJ_ALIAS = new HashMap<>();
|
||||
OBJ_ALIAS.put(Consts.CLASS_STRING, "str");
|
||||
OBJ_ALIAS.put(Consts.CLASS_CLASS, "cls");
|
||||
OBJ_ALIAS.put(Consts.CLASS_THROWABLE, "th");
|
||||
@@ -102,16 +103,11 @@ public class NameGen {
|
||||
if (fallback) {
|
||||
return getFallbackName(arg);
|
||||
}
|
||||
String name = arg.getName();
|
||||
String varName;
|
||||
if (name != null) {
|
||||
if ("this".equals(name)) {
|
||||
return name;
|
||||
}
|
||||
varName = name;
|
||||
} else {
|
||||
varName = guessName(arg);
|
||||
if (arg.isThis()) {
|
||||
return RegisterArg.THIS_ARG_NAME;
|
||||
}
|
||||
String name = arg.getName();
|
||||
String varName = name != null ? name : guessName(arg);
|
||||
if (NameMapper.isReserved(varName)) {
|
||||
return varName + "R";
|
||||
}
|
||||
@@ -141,11 +137,11 @@ public class NameGen {
|
||||
private String makeNameForType(ArgType type) {
|
||||
if (type.isPrimitive()) {
|
||||
return makeNameForPrimitive(type);
|
||||
} else if (type.isArray()) {
|
||||
return makeNameForType(type.getArrayRootElement()) + "Arr";
|
||||
} else {
|
||||
return makeNameForObject(type);
|
||||
}
|
||||
if (type.isArray()) {
|
||||
return makeNameForType(type.getArrayRootElement()) + "Arr";
|
||||
}
|
||||
return makeNameForObject(type);
|
||||
}
|
||||
|
||||
private static String makeNameForPrimitive(ArgType type) {
|
||||
@@ -158,12 +154,15 @@ public class NameGen {
|
||||
if (alias != null) {
|
||||
return alias;
|
||||
}
|
||||
ClassInfo extClsInfo = ClassInfo.extCls(mth.dex(), type);
|
||||
ClassInfo extClsInfo = ClassInfo.extCls(mth.root(), type);
|
||||
String shortName = extClsInfo.getShortName();
|
||||
String vName = fromName(shortName);
|
||||
if (vName != null) {
|
||||
return vName;
|
||||
}
|
||||
if (shortName != null) {
|
||||
return StringUtils.escape(shortName.toLowerCase());
|
||||
}
|
||||
}
|
||||
return StringUtils.escape(type.toString());
|
||||
}
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
package jadx.core.codegen;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.nodes.DeclareVariablesAttr;
|
||||
@@ -15,7 +21,7 @@ import jadx.core.dex.nodes.IBlock;
|
||||
import jadx.core.dex.nodes.IContainer;
|
||||
import jadx.core.dex.nodes.IRegion;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.parser.FieldValueAttr;
|
||||
import jadx.core.dex.nodes.parser.FieldInitAttr;
|
||||
import jadx.core.dex.regions.Region;
|
||||
import jadx.core.dex.regions.SwitchRegion;
|
||||
import jadx.core.dex.regions.SynchronizedRegion;
|
||||
@@ -32,12 +38,6 @@ import jadx.core.utils.RegionUtils;
|
||||
import jadx.core.utils.exceptions.CodegenException;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class RegionGen extends InsnGen {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(RegionGen.class);
|
||||
|
||||
@@ -134,17 +134,16 @@ public class RegionGen extends InsnGen {
|
||||
* Connect if-else-if block
|
||||
*/
|
||||
private boolean connectElseIf(CodeWriter code, IContainer els) throws CodegenException {
|
||||
if (!els.contains(AFlag.ELSE_IF_CHAIN)) {
|
||||
return false;
|
||||
}
|
||||
if (!(els instanceof Region)) {
|
||||
return false;
|
||||
}
|
||||
List<IContainer> subBlocks = ((Region) els).getSubBlocks();
|
||||
if (subBlocks.size() == 1
|
||||
&& subBlocks.get(0) instanceof IfRegion) {
|
||||
makeIf((IfRegion) subBlocks.get(0), code, false);
|
||||
return true;
|
||||
if (els.contains(AFlag.ELSE_IF_CHAIN) && els instanceof Region) {
|
||||
List<IContainer> subBlocks = ((Region) els).getSubBlocks();
|
||||
if (subBlocks.size() == 1) {
|
||||
IContainer elseBlock = subBlocks.get(0);
|
||||
if (elseBlock instanceof IfRegion) {
|
||||
declareVars(code, elseBlock);
|
||||
makeIf((IfRegion) elseBlock, code, false);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -154,7 +153,7 @@ public class RegionGen extends InsnGen {
|
||||
if (header != null) {
|
||||
List<InsnNode> headerInsns = header.getInstructions();
|
||||
if (headerInsns.size() > 1) {
|
||||
ErrorsCounter.methodError(mth, "Found not inlined instructions from loop header");
|
||||
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);
|
||||
@@ -207,11 +206,13 @@ public class RegionGen extends InsnGen {
|
||||
if (region.isConditionAtEnd()) {
|
||||
code.startLine("do {");
|
||||
makeRegionIndent(code, region.getBody());
|
||||
code.startLine("} while (");
|
||||
code.startLineWithNum(region.getConditionSourceLine());
|
||||
code.add("} while (");
|
||||
conditionGen.add(code, condition);
|
||||
code.add(");");
|
||||
} else {
|
||||
code.startLine("while (");
|
||||
code.startLineWithNum(region.getConditionSourceLine());
|
||||
code.add("while (");
|
||||
conditionGen.add(code, condition);
|
||||
code.add(") {");
|
||||
makeRegionIndent(code, region.getBody());
|
||||
@@ -249,13 +250,13 @@ public class RegionGen extends InsnGen {
|
||||
} else {
|
||||
staticField(code, fn.getFieldInfo());
|
||||
// print original value, sometimes replace with incorrect field
|
||||
FieldValueAttr valueAttr = fn.get(AType.FIELD_VALUE);
|
||||
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()));
|
||||
code.add(TypeGen.literalToString((Integer) k, arg.getType(), mth));
|
||||
} else {
|
||||
throw new JadxRuntimeException("Unexpected key in switch: " + (k != null ? k.getClass() : null));
|
||||
}
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
package jadx.core.codegen;
|
||||
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.PrimitiveType;
|
||||
import jadx.core.utils.StringUtils;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.core.deobf.NameMapper;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.PrimitiveType;
|
||||
import jadx.core.dex.nodes.IDexNode;
|
||||
import jadx.core.utils.StringUtils;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
public class TypeGen {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(TypeGen.class);
|
||||
|
||||
@@ -31,7 +34,16 @@ public class TypeGen {
|
||||
*
|
||||
* @throws JadxRuntimeException for incorrect type or literal value
|
||||
*/
|
||||
public static String literalToString(long lit, ArgType type, IDexNode dexNode) {
|
||||
return literalToString(lit, type, dexNode.root().getStringUtils());
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public static String literalToString(long lit, ArgType type) {
|
||||
return literalToString(lit, type, new StringUtils(new JadxArgs()));
|
||||
}
|
||||
|
||||
private static String literalToString(long lit, ArgType type, StringUtils stringUtils) {
|
||||
if (type == null || !type.isTypeKnown()) {
|
||||
String n = Long.toString(lit);
|
||||
if (Math.abs(lit) > 100) {
|
||||
@@ -46,7 +58,11 @@ public class TypeGen {
|
||||
case BOOLEAN:
|
||||
return lit == 0 ? "false" : "true";
|
||||
case CHAR:
|
||||
return StringUtils.unescapeChar((char) lit);
|
||||
char ch = (char) lit;
|
||||
if (!NameMapper.isPrintableChar(ch)) {
|
||||
return Integer.toString(ch);
|
||||
}
|
||||
return stringUtils.unescapeChar(ch);
|
||||
case BYTE:
|
||||
return formatByte((byte) lit);
|
||||
case SHORT:
|
||||
@@ -160,5 +176,4 @@ public class TypeGen {
|
||||
}
|
||||
return Float.toString(f) + "f";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
package jadx.core.deobf;
|
||||
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.info.FieldInfo;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
@@ -16,6 +12,10 @@ import org.apache.commons.io.FileUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.info.FieldInfo;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
|
||||
class DeobfPresets {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DeobfPresets.class);
|
||||
|
||||
@@ -24,9 +24,9 @@ class DeobfPresets {
|
||||
private final Deobfuscator deobfuscator;
|
||||
private final File deobfMapFile;
|
||||
|
||||
private final Map<String, String> clsPresetMap = new HashMap<String, String>();
|
||||
private final Map<String, String> fldPresetMap = new HashMap<String, String>();
|
||||
private final Map<String, String> mthPresetMap = new HashMap<String, String>();
|
||||
private final Map<String, String> clsPresetMap = new HashMap<>();
|
||||
private final Map<String, String> fldPresetMap = new HashMap<>();
|
||||
private final Map<String, String> mthPresetMap = new HashMap<>();
|
||||
|
||||
public DeobfPresets(Deobfuscator deobfuscator, File deobfMapFile) {
|
||||
this.deobfuscator = deobfuscator;
|
||||
@@ -98,7 +98,7 @@ class DeobfPresets {
|
||||
* Saves DefaultDeobfuscator presets
|
||||
*/
|
||||
private void dumpMapping() throws IOException {
|
||||
List<String> list = new ArrayList<String>();
|
||||
List<String> list = new ArrayList<>();
|
||||
// packages
|
||||
for (PackageNode p : deobfuscator.getRootPackage().getInnerPackages()) {
|
||||
for (PackageNode pp : p.getInnerPackages()) {
|
||||
|
||||
@@ -1,18 +1,11 @@
|
||||
package jadx.core.deobf;
|
||||
|
||||
import jadx.api.IJadxArgs;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.nodes.SourceFileAttr;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.info.FieldInfo;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.DexNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
@@ -23,6 +16,18 @@ import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.nodes.SourceFileAttr;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.info.FieldInfo;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.DexNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
|
||||
public class Deobfuscator {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(Deobfuscator.class);
|
||||
|
||||
@@ -31,31 +36,38 @@ public class Deobfuscator {
|
||||
public static final String CLASS_NAME_SEPARATOR = ".";
|
||||
public static final String INNER_CLASS_SEPARATOR = "$";
|
||||
|
||||
private final IJadxArgs args;
|
||||
private final JadxArgs args;
|
||||
@NotNull
|
||||
private final List<DexNode> dexNodes;
|
||||
private final DeobfPresets deobfPresets;
|
||||
|
||||
private final Map<ClassInfo, DeobfClsInfo> clsMap = new HashMap<ClassInfo, DeobfClsInfo>();
|
||||
private final Map<FieldInfo, String> fldMap = new HashMap<FieldInfo, String>();
|
||||
private final Map<MethodInfo, String> mthMap = new HashMap<MethodInfo, String>();
|
||||
private final Map<ClassInfo, DeobfClsInfo> clsMap = new HashMap<>();
|
||||
private final Map<FieldInfo, String> fldMap = 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 Set<String> pkgSet = new TreeSet<String>();
|
||||
private final Set<String> pkgSet = new TreeSet<>();
|
||||
private final Set<String> reservedClsNames = new HashSet<>();
|
||||
|
||||
private final int maxLength;
|
||||
private final int minLength;
|
||||
private final boolean useSourceNameAsAlias;
|
||||
|
||||
private int pkgIndex = 0;
|
||||
private int clsIndex = 0;
|
||||
private int fldIndex = 0;
|
||||
private int mthIndex = 0;
|
||||
|
||||
public Deobfuscator(IJadxArgs args, @NotNull List<DexNode> dexNodes, File deobfMapFile) {
|
||||
public Deobfuscator(JadxArgs args, @NotNull List<DexNode> dexNodes, File deobfMapFile) {
|
||||
this.args = args;
|
||||
this.dexNodes = dexNodes;
|
||||
|
||||
this.minLength = args.getDeobfuscationMinLength();
|
||||
this.maxLength = args.getDeobfuscationMaxLength();
|
||||
this.useSourceNameAsAlias = args.isUseSourceNameAsClassAlias();
|
||||
|
||||
this.deobfPresets = new DeobfPresets(this, deobfMapFile);
|
||||
}
|
||||
@@ -80,7 +92,12 @@ public class Deobfuscator {
|
||||
private void preProcess() {
|
||||
for (DexNode dexNode : dexNodes) {
|
||||
for (ClassNode cls : dexNode.getClasses()) {
|
||||
doClass(cls);
|
||||
Collections.addAll(reservedClsNames, cls.getPackage().split("\\."));
|
||||
}
|
||||
}
|
||||
for (DexNode dexNode : dexNodes) {
|
||||
for (ClassNode cls : dexNode.getClasses()) {
|
||||
preProcessClass(cls);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -92,9 +109,35 @@ public class Deobfuscator {
|
||||
}
|
||||
for (DexNode dexNode : dexNodes) {
|
||||
for (ClassNode cls : dexNode.getClasses()) {
|
||||
processClass(dexNode, cls);
|
||||
processClass(cls);
|
||||
}
|
||||
}
|
||||
postProcess();
|
||||
}
|
||||
|
||||
private void postProcess() {
|
||||
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.isRenamed() && !mth.isAliasFromPreset()) {
|
||||
mth.setAlias(String.format("mo%d%s", id, makeName(mth.getName())));
|
||||
}
|
||||
aliasToUse = mth.getAlias();
|
||||
}
|
||||
mth.setAlias(aliasToUse);
|
||||
mth.setAliasFromPreset(aliasFromPreset);
|
||||
}
|
||||
id++;
|
||||
}
|
||||
}
|
||||
|
||||
void clear() {
|
||||
@@ -102,27 +145,111 @@ public class Deobfuscator {
|
||||
clsMap.clear();
|
||||
fldMap.clear();
|
||||
mthMap.clear();
|
||||
|
||||
ovrd.clear();
|
||||
ovrdMap.clear();
|
||||
}
|
||||
|
||||
private void processClass(DexNode dex, ClassNode cls) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void processClass(ClassNode cls) {
|
||||
ClassInfo clsInfo = cls.getClassInfo();
|
||||
String fullName = getClassFullName(clsInfo);
|
||||
if (!fullName.equals(clsInfo.getFullName())) {
|
||||
clsInfo.rename(dex, fullName);
|
||||
clsInfo.rename(cls.dex().root(), fullName);
|
||||
}
|
||||
for (FieldNode field : cls.getFields()) {
|
||||
FieldInfo fieldInfo = field.getFieldInfo();
|
||||
String alias = getFieldAlias(field);
|
||||
if (alias != null) {
|
||||
fieldInfo.setAlias(alias);
|
||||
}
|
||||
renameField(field);
|
||||
}
|
||||
for (MethodNode mth : cls.getMethods()) {
|
||||
MethodInfo methodInfo = mth.getMethodInfo();
|
||||
String alias = getMethodAlias(mth);
|
||||
if (alias != null) {
|
||||
methodInfo.setAlias(alias);
|
||||
}
|
||||
renameMethod(mth);
|
||||
}
|
||||
for (ClassNode innerCls : cls.getInnerClasses()) {
|
||||
processClass(innerCls);
|
||||
}
|
||||
}
|
||||
|
||||
public void renameField(FieldNode field) {
|
||||
FieldInfo fieldInfo = field.getFieldInfo();
|
||||
String alias = getFieldAlias(field);
|
||||
if (alias != null) {
|
||||
fieldInfo.setAlias(alias);
|
||||
}
|
||||
}
|
||||
|
||||
public void renameMethod(MethodNode mth) {
|
||||
String alias = getMethodAlias(mth);
|
||||
if (alias != null) {
|
||||
mth.getMethodInfo().setAlias(alias);
|
||||
}
|
||||
if (mth.isVirtual()) {
|
||||
resolveOverriding(mth);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,7 +310,7 @@ public class Deobfuscator {
|
||||
return prefix + clsInfo.getShortName();
|
||||
}
|
||||
|
||||
private void doClass(ClassNode cls) {
|
||||
private void preProcessClass(ClassNode cls) {
|
||||
ClassInfo classInfo = cls.getClassInfo();
|
||||
String pkgFullName = classInfo.getPackage();
|
||||
PackageNode pkg = getPackageNode(pkgFullName, true);
|
||||
@@ -192,13 +319,16 @@ public class Deobfuscator {
|
||||
String alias = deobfPresets.getForCls(classInfo);
|
||||
if (alias != null) {
|
||||
clsMap.put(classInfo, new DeobfClsInfo(this, cls, pkg, alias));
|
||||
return;
|
||||
} else {
|
||||
if (!clsMap.containsKey(classInfo)) {
|
||||
String clsShortName = classInfo.getShortName();
|
||||
if (shouldRename(clsShortName) || reservedClsNames.contains(clsShortName)) {
|
||||
makeClsAlias(cls);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (clsMap.containsKey(classInfo)) {
|
||||
return;
|
||||
}
|
||||
if (shouldRename(classInfo.getShortName())) {
|
||||
makeClsAlias(cls);
|
||||
for (ClassNode innerCls : cls.getInnerClasses()) {
|
||||
preProcessClass(innerCls);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,7 +342,12 @@ public class Deobfuscator {
|
||||
|
||||
private String makeClsAlias(ClassNode cls) {
|
||||
ClassInfo classInfo = cls.getClassInfo();
|
||||
String alias = getAliasFromSourceFile(cls);
|
||||
String alias = null;
|
||||
|
||||
if (this.useSourceNameAsAlias) {
|
||||
alias = getAliasFromSourceFile(cls);
|
||||
}
|
||||
|
||||
if (alias == null) {
|
||||
String clsName = classInfo.getShortName();
|
||||
alias = String.format("C%04d%s", clsIndex++, makeName(clsName));
|
||||
@@ -228,21 +363,33 @@ public class Deobfuscator {
|
||||
if (sourceFileAttr == null) {
|
||||
return null;
|
||||
}
|
||||
if (cls.getClassInfo().isInner()) {
|
||||
return null;
|
||||
}
|
||||
String name = sourceFileAttr.getFileName();
|
||||
if (name.endsWith(".java")) {
|
||||
name = name.substring(0, name.length() - ".java".length());
|
||||
} else if (name.endsWith(".kt")) {
|
||||
name = name.substring(0, name.length() - ".kt".length());
|
||||
}
|
||||
if (NameMapper.isValidIdentifier(name)
|
||||
&& !NameMapper.isReserved(name)) {
|
||||
// TODO: check if no class with this name exists or already renamed
|
||||
cls.remove(AType.SOURCE_FILE);
|
||||
return name;
|
||||
if (!NameMapper.isValidIdentifier(name) || NameMapper.isReserved(name)) {
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
for (DeobfClsInfo deobfClsInfo : clsMap.values()) {
|
||||
if (deobfClsInfo.getAlias().equals(name)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
ClassNode otherCls = cls.dex().root().searchClassByName(cls.getPackage() + "." + name);
|
||||
if (otherCls != null) {
|
||||
return null;
|
||||
}
|
||||
cls.remove(AType.SOURCE_FILE);
|
||||
return name;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getFieldAlias(FieldNode field) {
|
||||
private String getFieldAlias(FieldNode field) {
|
||||
FieldInfo fieldInfo = field.getFieldInfo();
|
||||
String alias = fldMap.get(fieldInfo);
|
||||
if (alias != null) {
|
||||
@@ -260,7 +407,7 @@ public class Deobfuscator {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getMethodAlias(MethodNode mth) {
|
||||
private String getMethodAlias(MethodNode mth) {
|
||||
MethodInfo methodInfo = mth.getMethodInfo();
|
||||
String alias = mthMap.get(methodInfo);
|
||||
if (alias != null) {
|
||||
@@ -269,6 +416,7 @@ public class Deobfuscator {
|
||||
alias = deobfPresets.getForMth(methodInfo);
|
||||
if (alias != null) {
|
||||
mthMap.put(methodInfo, alias);
|
||||
methodInfo.setAliasFromPreset(true);
|
||||
return alias;
|
||||
}
|
||||
if (shouldRename(mth.getName())) {
|
||||
@@ -304,9 +452,9 @@ public class Deobfuscator {
|
||||
parentPkg = parentPkg.getParentPackage();
|
||||
}
|
||||
|
||||
final String pkgName = pkg.getName();
|
||||
String pkgName = pkg.getName();
|
||||
if (!pkg.hasAlias() && shouldRename(pkgName)) {
|
||||
final String pkgAlias = String.format("p%03d%s", pkgIndex++, makeName(pkgName));
|
||||
String pkgAlias = String.format("p%03d%s", pkgIndex++, makeName(pkgName));
|
||||
pkg.setAlias(pkgAlias);
|
||||
}
|
||||
}
|
||||
@@ -363,7 +511,7 @@ public class Deobfuscator {
|
||||
}
|
||||
|
||||
private String getPackageName(String packageName) {
|
||||
final PackageNode pkg = getPackageNode(packageName, false);
|
||||
PackageNode pkg = getPackageNode(packageName, false);
|
||||
if (pkg != null) {
|
||||
return pkg.getFullAlias();
|
||||
}
|
||||
@@ -371,7 +519,7 @@ public class Deobfuscator {
|
||||
}
|
||||
|
||||
private String getClassName(ClassInfo clsInfo) {
|
||||
final DeobfClsInfo deobfClsInfo = clsMap.get(clsInfo);
|
||||
DeobfClsInfo deobfClsInfo = clsMap.get(clsInfo);
|
||||
if (deobfClsInfo != null) {
|
||||
return deobfClsInfo.makeNameWithoutPkg();
|
||||
}
|
||||
@@ -383,7 +531,7 @@ public class Deobfuscator {
|
||||
}
|
||||
|
||||
private String getClassFullName(ClassInfo clsInfo) {
|
||||
final DeobfClsInfo deobfClsInfo = clsMap.get(clsInfo);
|
||||
DeobfClsInfo deobfClsInfo = clsMap.get(clsInfo);
|
||||
if (deobfClsInfo != null) {
|
||||
return deobfClsInfo.getFullName();
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static jadx.core.utils.StringUtils.notEmpty;
|
||||
|
||||
public class NameMapper {
|
||||
|
||||
private static final Pattern VALID_JAVA_IDENTIFIER = Pattern.compile(
|
||||
@@ -13,8 +15,8 @@ public class NameMapper {
|
||||
private static final Pattern VALID_JAVA_FULL_IDENTIFIER = Pattern.compile(
|
||||
"(" + VALID_JAVA_IDENTIFIER + "\\.)*" + VALID_JAVA_IDENTIFIER);
|
||||
|
||||
private static final Set<String> RESERVED_NAMES = new HashSet<String>(
|
||||
Arrays.asList(new String[]{
|
||||
private static final Set<String> RESERVED_NAMES = new HashSet<>(
|
||||
Arrays.asList(
|
||||
"abstract",
|
||||
"assert",
|
||||
"boolean",
|
||||
@@ -67,8 +69,8 @@ public class NameMapper {
|
||||
"try",
|
||||
"void",
|
||||
"volatile",
|
||||
"while",
|
||||
})
|
||||
"while"
|
||||
)
|
||||
);
|
||||
|
||||
public static boolean isReserved(String str) {
|
||||
@@ -76,11 +78,15 @@ public class NameMapper {
|
||||
}
|
||||
|
||||
public static boolean isValidIdentifier(String str) {
|
||||
return VALID_JAVA_IDENTIFIER.matcher(str).matches() && isAllCharsPrintable(str);
|
||||
return notEmpty(str)
|
||||
&& VALID_JAVA_IDENTIFIER.matcher(str).matches()
|
||||
&& isAllCharsPrintable(str);
|
||||
}
|
||||
|
||||
public static boolean isValidFullIdentifier(String str) {
|
||||
return VALID_JAVA_FULL_IDENTIFIER.matcher(str).matches() && isAllCharsPrintable(str);
|
||||
return notEmpty(str)
|
||||
&& VALID_JAVA_FULL_IDENTIFIER.matcher(str).matches()
|
||||
&& isAllCharsPrintable(str);
|
||||
}
|
||||
|
||||
public static boolean isPrintableChar(int c) {
|
||||
@@ -96,4 +102,7 @@ public class NameMapper {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private NameMapper() {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package jadx.core.deobf;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
|
||||
class OverridedMethodsNode {
|
||||
|
||||
private Set<MethodInfo> methods;
|
||||
|
||||
public OverridedMethodsNode(Set<MethodInfo> methodsSet) {
|
||||
methods = methodsSet;
|
||||
}
|
||||
|
||||
public boolean contains(MethodInfo mth) {
|
||||
return methods.contains(mth);
|
||||
}
|
||||
|
||||
public void add(MethodInfo mth) {
|
||||
methods.add(mth);
|
||||
}
|
||||
|
||||
public Set<MethodInfo> getMethods() {
|
||||
return methods;
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
package jadx.core.deobf;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Deque;
|
||||
import java.util.List;
|
||||
import java.util.Stack;
|
||||
|
||||
public class PackageNode {
|
||||
|
||||
@@ -29,11 +30,11 @@ public class PackageNode {
|
||||
|
||||
public String getFullName() {
|
||||
if (cachedPackageFullName == null) {
|
||||
Stack<PackageNode> pp = getParentPackages();
|
||||
Deque<PackageNode> pp = getParentPackages();
|
||||
|
||||
StringBuilder result = new StringBuilder();
|
||||
result.append(pp.pop().getName());
|
||||
while (pp.size() > 0) {
|
||||
while (!pp.isEmpty()) {
|
||||
result.append(SEPARATOR_CHAR);
|
||||
result.append(pp.pop().getName());
|
||||
}
|
||||
@@ -59,12 +60,17 @@ public class PackageNode {
|
||||
|
||||
public String getFullAlias() {
|
||||
if (cachedPackageFullAlias == null) {
|
||||
Stack<PackageNode> pp = getParentPackages();
|
||||
Deque<PackageNode> pp = getParentPackages();
|
||||
StringBuilder result = new StringBuilder();
|
||||
result.append(pp.pop().getAlias());
|
||||
while (pp.size() > 0) {
|
||||
result.append(SEPARATOR_CHAR);
|
||||
|
||||
if (!pp.isEmpty()) {
|
||||
result.append(pp.pop().getAlias());
|
||||
while (!pp.isEmpty()) {
|
||||
result.append(SEPARATOR_CHAR);
|
||||
result.append(pp.pop().getAlias());
|
||||
}
|
||||
} else {
|
||||
result.append(this.getAlias());
|
||||
}
|
||||
cachedPackageFullAlias = result.toString();
|
||||
}
|
||||
@@ -81,7 +87,7 @@ public class PackageNode {
|
||||
|
||||
public void addInnerPackage(PackageNode pkg) {
|
||||
if (innerPackages.isEmpty()) {
|
||||
innerPackages = new ArrayList<PackageNode>();
|
||||
innerPackages = new ArrayList<>();
|
||||
}
|
||||
innerPackages.add(pkg);
|
||||
pkg.parentPackage = this;
|
||||
@@ -109,16 +115,15 @@ public class PackageNode {
|
||||
*
|
||||
* @return stack with parent packages
|
||||
*/
|
||||
private Stack<PackageNode> getParentPackages() {
|
||||
Stack<PackageNode> pp = new Stack<PackageNode>();
|
||||
private Deque<PackageNode> getParentPackages() {
|
||||
Deque<PackageNode> pp = new ArrayDeque<>();
|
||||
|
||||
PackageNode currentP = this;
|
||||
PackageNode parentP = currentP.getParentPackage();
|
||||
|
||||
while (currentP != parentP) {
|
||||
pp.push(currentP);
|
||||
currentP = parentP;
|
||||
parentP = currentP.getParentPackage();
|
||||
PackageNode currentPkg = this;
|
||||
PackageNode parentPkg = currentPkg.getParentPackage();
|
||||
while (currentPkg != parentPkg) {
|
||||
pp.push(currentPkg);
|
||||
currentPkg = parentPkg;
|
||||
parentPkg = currentPkg.getParentPackage();
|
||||
}
|
||||
return pp;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ public enum AFlag {
|
||||
LOOP_END,
|
||||
|
||||
SYNTHETIC,
|
||||
FINAL, // SSAVar attribute for make var final
|
||||
|
||||
RETURN, // block contains only return instruction
|
||||
ORIG_RETURN,
|
||||
@@ -22,8 +23,10 @@ public enum AFlag {
|
||||
REMOVE,
|
||||
|
||||
SKIP_FIRST_ARG,
|
||||
SKIP_ARG, // skip argument in invoke call
|
||||
ANONYMOUS_CONSTRUCTOR,
|
||||
ANONYMOUS_CLASS,
|
||||
THIS,
|
||||
|
||||
ELSE_IF_CHAIN,
|
||||
|
||||
|
||||
@@ -3,19 +3,21 @@ package jadx.core.dex.attributes;
|
||||
import jadx.core.dex.attributes.annotations.AnnotationsList;
|
||||
import jadx.core.dex.attributes.annotations.MethodParameters;
|
||||
import jadx.core.dex.attributes.nodes.DeclareVariablesAttr;
|
||||
import jadx.core.dex.attributes.nodes.EdgeInsnAttr;
|
||||
import jadx.core.dex.attributes.nodes.EnumClassAttr;
|
||||
import jadx.core.dex.attributes.nodes.EnumMapAttr;
|
||||
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
|
||||
import jadx.core.dex.attributes.nodes.ForceReturnAttr;
|
||||
import jadx.core.dex.attributes.nodes.IgnoreEdgeAttr;
|
||||
import jadx.core.dex.attributes.nodes.JadxErrorAttr;
|
||||
import jadx.core.dex.attributes.nodes.JadxError;
|
||||
import jadx.core.dex.attributes.nodes.JadxWarn;
|
||||
import jadx.core.dex.attributes.nodes.JumpInfo;
|
||||
import jadx.core.dex.attributes.nodes.LoopInfo;
|
||||
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
|
||||
import jadx.core.dex.attributes.nodes.MethodInlineAttr;
|
||||
import jadx.core.dex.attributes.nodes.PhiListAttr;
|
||||
import jadx.core.dex.attributes.nodes.SourceFileAttr;
|
||||
import jadx.core.dex.nodes.parser.FieldValueAttr;
|
||||
import jadx.core.dex.nodes.parser.FieldInitAttr;
|
||||
import jadx.core.dex.trycatch.CatchAttr;
|
||||
import jadx.core.dex.trycatch.ExcHandlerAttr;
|
||||
import jadx.core.dex.trycatch.SplitterBlockAttr;
|
||||
@@ -28,24 +30,27 @@ import jadx.core.dex.trycatch.SplitterBlockAttr;
|
||||
*/
|
||||
public class AType<T extends IAttribute> {
|
||||
|
||||
public static final AType<AttrList<JumpInfo>> JUMP = new AType<AttrList<JumpInfo>>();
|
||||
public static final AType<AttrList<LoopInfo>> LOOP = new AType<AttrList<LoopInfo>>();
|
||||
public static final AType<AttrList<JumpInfo>> JUMP = new AType<>();
|
||||
public static final AType<AttrList<LoopInfo>> LOOP = new AType<>();
|
||||
public static final AType<AttrList<EdgeInsnAttr>> EDGE_INSN = new AType<>();
|
||||
|
||||
public static final AType<ExcHandlerAttr> EXC_HANDLER = new AType<ExcHandlerAttr>();
|
||||
public static final AType<CatchAttr> CATCH_BLOCK = new AType<CatchAttr>();
|
||||
public static final AType<SplitterBlockAttr> SPLITTER_BLOCK = new AType<SplitterBlockAttr>();
|
||||
public static final AType<ForceReturnAttr> FORCE_RETURN = new AType<ForceReturnAttr>();
|
||||
public static final AType<FieldValueAttr> FIELD_VALUE = new AType<FieldValueAttr>();
|
||||
public static final AType<FieldReplaceAttr> FIELD_REPLACE = new AType<FieldReplaceAttr>();
|
||||
public static final AType<JadxErrorAttr> JADX_ERROR = new AType<JadxErrorAttr>();
|
||||
public static final AType<MethodInlineAttr> METHOD_INLINE = new AType<MethodInlineAttr>();
|
||||
public static final AType<EnumClassAttr> ENUM_CLASS = new AType<EnumClassAttr>();
|
||||
public static final AType<EnumMapAttr> ENUM_MAP = new AType<EnumMapAttr>();
|
||||
public static final AType<AnnotationsList> ANNOTATION_LIST = new AType<AnnotationsList>();
|
||||
public static final AType<MethodParameters> ANNOTATION_MTH_PARAMETERS = new AType<MethodParameters>();
|
||||
public static final AType<PhiListAttr> PHI_LIST = new AType<PhiListAttr>();
|
||||
public static final AType<SourceFileAttr> SOURCE_FILE = new AType<SourceFileAttr>();
|
||||
public static final AType<DeclareVariablesAttr> DECLARE_VARIABLES = new AType<DeclareVariablesAttr>();
|
||||
public static final AType<LoopLabelAttr> LOOP_LABEL = new AType<LoopLabelAttr>();
|
||||
public static final AType<IgnoreEdgeAttr> IGNORE_EDGE = new AType<IgnoreEdgeAttr>();
|
||||
public static final AType<AttrList<JadxError>> JADX_ERROR = new AType<>();
|
||||
public static final AType<AttrList<JadxWarn>> JADX_WARN = new AType<>();
|
||||
|
||||
public static final AType<ExcHandlerAttr> EXC_HANDLER = new AType<>();
|
||||
public static final AType<CatchAttr> CATCH_BLOCK = new AType<>();
|
||||
public static final AType<SplitterBlockAttr> SPLITTER_BLOCK = new AType<>();
|
||||
public static final AType<ForceReturnAttr> FORCE_RETURN = new AType<>();
|
||||
public static final AType<FieldInitAttr> FIELD_INIT = new AType<>();
|
||||
public static final AType<FieldReplaceAttr> FIELD_REPLACE = new AType<>();
|
||||
public static final AType<MethodInlineAttr> METHOD_INLINE = new AType<>();
|
||||
public static final AType<EnumClassAttr> ENUM_CLASS = new AType<>();
|
||||
public static final AType<EnumMapAttr> ENUM_MAP = new AType<>();
|
||||
public static final AType<AnnotationsList> ANNOTATION_LIST = new AType<>();
|
||||
public static final AType<MethodParameters> ANNOTATION_MTH_PARAMETERS = new AType<>();
|
||||
public static final AType<PhiListAttr> PHI_LIST = new AType<>();
|
||||
public static final AType<SourceFileAttr> SOURCE_FILE = new AType<>();
|
||||
public static final AType<DeclareVariablesAttr> DECLARE_VARIABLES = new AType<>();
|
||||
public static final AType<LoopLabelAttr> LOOP_LABEL = new AType<>();
|
||||
public static final AType<IgnoreEdgeAttr> IGNORE_EDGE = new AType<>();
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
package jadx.core.dex.attributes;
|
||||
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
public class AttrList<T> implements IAttribute {
|
||||
|
||||
private final AType<AttrList<T>> type;
|
||||
private final List<T> list = new LinkedList<T>();
|
||||
private final List<T> list = new ArrayList<>();
|
||||
|
||||
public AttrList(AType<AttrList<T>> type) {
|
||||
this.type = type;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package jadx.core.dex.attributes;
|
||||
|
||||
import jadx.core.dex.attributes.annotations.Annotation;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import jadx.core.dex.attributes.annotations.Annotation;
|
||||
|
||||
public abstract class AttrNode implements IAttributeNode {
|
||||
|
||||
private static final AttributeStorage EMPTY_ATTR_STORAGE = new EmptyAttrStorage();
|
||||
@@ -27,10 +27,13 @@ public abstract class AttrNode implements IAttributeNode {
|
||||
|
||||
@Override
|
||||
public void copyAttributesFrom(AttrNode attrNode) {
|
||||
initStorage().addAll(attrNode.storage);
|
||||
AttributeStorage copyFrom = attrNode.storage;
|
||||
if (!copyFrom.isEmpty()) {
|
||||
initStorage().addAll(copyFrom);
|
||||
}
|
||||
}
|
||||
|
||||
AttributeStorage initStorage() {
|
||||
private AttributeStorage initStorage() {
|
||||
AttributeStorage store = storage;
|
||||
if (store == EMPTY_ATTR_STORAGE) {
|
||||
store = new AttributeStorage();
|
||||
@@ -93,4 +96,8 @@ public abstract class AttrNode implements IAttributeNode {
|
||||
public String getAttributesString() {
|
||||
return storage.toString();
|
||||
}
|
||||
|
||||
public boolean isAttrStorageEmpty() {
|
||||
return storage.isEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
package jadx.core.dex.attributes;
|
||||
|
||||
import jadx.core.dex.attributes.annotations.Annotation;
|
||||
import jadx.core.dex.attributes.annotations.AnnotationsList;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
@@ -12,6 +8,10 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import jadx.core.dex.attributes.annotations.Annotation;
|
||||
import jadx.core.dex.attributes.annotations.AnnotationsList;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
/**
|
||||
* Storage for different attribute types:
|
||||
* 1. flags - boolean attribute (set or not)
|
||||
@@ -24,7 +24,7 @@ public class AttributeStorage {
|
||||
|
||||
public AttributeStorage() {
|
||||
flags = EnumSet.noneOf(AFlag.class);
|
||||
attributes = new IdentityHashMap<AType<?>, IAttribute>();
|
||||
attributes = new IdentityHashMap<>();
|
||||
}
|
||||
|
||||
public void add(AFlag flag) {
|
||||
@@ -38,7 +38,7 @@ public class AttributeStorage {
|
||||
public <T> void add(AType<AttrList<T>> type, T obj) {
|
||||
AttrList<T> list = get(type);
|
||||
if (list == null) {
|
||||
list = new AttrList<T>(type);
|
||||
list = new AttrList<>(type);
|
||||
add(list);
|
||||
}
|
||||
list.getList().add(obj);
|
||||
@@ -84,7 +84,7 @@ public class AttributeStorage {
|
||||
}
|
||||
|
||||
public void remove(IAttribute attr) {
|
||||
AType<?> type = attr.getType();
|
||||
AType<? extends IAttribute> type = attr.getType();
|
||||
IAttribute a = attributes.get(type);
|
||||
if (a == attr) {
|
||||
attributes.remove(type);
|
||||
@@ -101,7 +101,7 @@ public class AttributeStorage {
|
||||
if (size == 0) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<String> list = new ArrayList<String>(size);
|
||||
List<String> list = new ArrayList<>(size);
|
||||
for (AFlag a : flags) {
|
||||
list.add(a.toString());
|
||||
}
|
||||
@@ -111,6 +111,10 @@ public class AttributeStorage {
|
||||
return list;
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return flags.isEmpty() && attributes.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
List<String> list = getAttributeStrings();
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package jadx.core.dex.attributes;
|
||||
|
||||
import jadx.core.dex.attributes.annotations.Annotation;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import jadx.core.dex.attributes.annotations.Annotation;
|
||||
|
||||
public final class EmptyAttrStorage extends AttributeStorage {
|
||||
|
||||
@Override
|
||||
@@ -53,6 +53,11 @@ public final class EmptyAttrStorage extends AttributeStorage {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "";
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package jadx.core.dex.attributes;
|
||||
|
||||
public interface IAttribute {
|
||||
|
||||
AType<?> getType();
|
||||
|
||||
AType<? extends IAttribute> getType();
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package jadx.core.dex.attributes;
|
||||
|
||||
import jadx.core.dex.attributes.annotations.Annotation;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import jadx.core.dex.attributes.annotations.Annotation;
|
||||
|
||||
public interface IAttributeNode {
|
||||
|
||||
void add(AFlag flag);
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package jadx.core.dex.attributes.annotations;
|
||||
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
|
||||
public class Annotation {
|
||||
|
||||
public enum Visibility {
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
package jadx.core.dex.attributes.annotations;
|
||||
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.IAttribute;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.IAttribute;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
public class AnnotationsList implements IAttribute {
|
||||
|
||||
public static final AnnotationsList EMPTY = new AnnotationsList(Collections.<Annotation>emptyList());
|
||||
@@ -17,7 +17,7 @@ public class AnnotationsList implements IAttribute {
|
||||
private final Map<String, Annotation> map;
|
||||
|
||||
public AnnotationsList(List<Annotation> anList) {
|
||||
map = new HashMap<String, Annotation>(anList.size());
|
||||
map = new HashMap<>(anList.size());
|
||||
for (Annotation a : anList) {
|
||||
map.put(a.getAnnotationClass(), a);
|
||||
}
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
package jadx.core.dex.attributes.annotations;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.IAttribute;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class MethodParameters implements IAttribute {
|
||||
|
||||
private final List<AnnotationsList> paramList;
|
||||
|
||||
public MethodParameters(int paramCount) {
|
||||
paramList = new ArrayList<AnnotationsList>(paramCount);
|
||||
paramList = new ArrayList<>(paramCount);
|
||||
}
|
||||
|
||||
public List<AnnotationsList> getParamList() {
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
package jadx.core.dex.attributes.nodes;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.IAttribute;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* List of variables to be declared at region start.
|
||||
*/
|
||||
public class DeclareVariablesAttr implements IAttribute {
|
||||
|
||||
private final List<RegisterArg> vars = new LinkedList<RegisterArg>();
|
||||
private final List<RegisterArg> vars = new LinkedList<>();
|
||||
|
||||
public Iterable<RegisterArg> getVars() {
|
||||
return vars;
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
package jadx.core.dex.attributes.nodes;
|
||||
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.AttrList;
|
||||
import jadx.core.dex.attributes.IAttribute;
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
|
||||
public class EdgeInsnAttr implements IAttribute {
|
||||
|
||||
private final BlockNode start;
|
||||
private final BlockNode end;
|
||||
private final InsnNode insn;
|
||||
|
||||
public static void addEdgeInsn(BlockNode start, BlockNode end, InsnNode insn) {
|
||||
EdgeInsnAttr edgeInsnAttr = new EdgeInsnAttr(start, end, insn);
|
||||
start.addAttr(AType.EDGE_INSN, edgeInsnAttr);
|
||||
end.addAttr(AType.EDGE_INSN, edgeInsnAttr);
|
||||
}
|
||||
|
||||
public EdgeInsnAttr(BlockNode start, BlockNode end, InsnNode insn) {
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
this.insn = insn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AType<AttrList<EdgeInsnAttr>> getType() {
|
||||
return AType.EDGE_INSN;
|
||||
}
|
||||
|
||||
public BlockNode getStart() {
|
||||
return start;
|
||||
}
|
||||
|
||||
public BlockNode getEnd() {
|
||||
return end;
|
||||
}
|
||||
|
||||
public InsnNode getInsn() {
|
||||
return insn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "EDGE_INSN: " + start + "->" + end + " " + insn;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
package jadx.core.dex.attributes.nodes;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.IAttribute;
|
||||
import jadx.core.dex.info.FieldInfo;
|
||||
@@ -7,9 +10,6 @@ import jadx.core.dex.instructions.mods.ConstructorInsn;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class EnumClassAttr implements IAttribute {
|
||||
|
||||
public static class EnumField {
|
||||
@@ -54,7 +54,7 @@ public class EnumClassAttr implements IAttribute {
|
||||
private MethodNode staticMethod;
|
||||
|
||||
public EnumClassAttr(int fieldsCount) {
|
||||
this.fields = new ArrayList<EnumField>(fieldsCount);
|
||||
this.fields = new ArrayList<>(fieldsCount);
|
||||
}
|
||||
|
||||
public List<EnumField> getFields() {
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
package jadx.core.dex.attributes.nodes;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.IAttribute;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class EnumMapAttr implements IAttribute {
|
||||
|
||||
public static class KeyValueMap {
|
||||
private final Map<Object, Object> map = new HashMap<Object, Object>();
|
||||
private final Map<Object, Object> map = new HashMap<>();
|
||||
|
||||
public Object get(Object key) {
|
||||
return map.get(key);
|
||||
@@ -21,9 +23,14 @@ public class EnumMapAttr implements IAttribute {
|
||||
}
|
||||
}
|
||||
|
||||
private final Map<FieldNode, KeyValueMap> fieldsMap = new HashMap<FieldNode, KeyValueMap>();
|
||||
@Nullable
|
||||
private Map<FieldNode, KeyValueMap> fieldsMap;
|
||||
|
||||
@Nullable
|
||||
public KeyValueMap getMap(FieldNode field) {
|
||||
if (fieldsMap == null) {
|
||||
return null;
|
||||
}
|
||||
return fieldsMap.get(field);
|
||||
}
|
||||
|
||||
@@ -31,11 +38,18 @@ public class EnumMapAttr implements IAttribute {
|
||||
KeyValueMap map = getMap(field);
|
||||
if (map == null) {
|
||||
map = new KeyValueMap();
|
||||
if (fieldsMap == null) {
|
||||
fieldsMap = new HashMap<>();
|
||||
}
|
||||
fieldsMap.put(field, map);
|
||||
}
|
||||
map.put(key, value);
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return fieldsMap == null || fieldsMap.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AType<EnumMapAttr> getType() {
|
||||
return AType.ENUM_MAP;
|
||||
@@ -45,5 +59,4 @@ public class EnumMapAttr implements IAttribute {
|
||||
public String toString() {
|
||||
return "Enum fields map: " + fieldsMap;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,24 +2,39 @@ package jadx.core.dex.attributes.nodes;
|
||||
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.IAttribute;
|
||||
import jadx.core.dex.info.FieldInfo;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
|
||||
public class FieldReplaceAttr implements IAttribute {
|
||||
|
||||
private final FieldInfo fieldInfo;
|
||||
private final boolean isOuterClass;
|
||||
|
||||
public FieldReplaceAttr(FieldInfo fieldInfo, boolean isOuterClass) {
|
||||
this.fieldInfo = fieldInfo;
|
||||
this.isOuterClass = isOuterClass;
|
||||
public enum ReplaceWith {
|
||||
CLASS_INSTANCE,
|
||||
VAR
|
||||
}
|
||||
|
||||
public FieldInfo getFieldInfo() {
|
||||
return fieldInfo;
|
||||
private final ReplaceWith replaceType;
|
||||
private final Object replaceObj;
|
||||
|
||||
public FieldReplaceAttr(ClassInfo cls) {
|
||||
this.replaceType = ReplaceWith.CLASS_INSTANCE;
|
||||
this.replaceObj = cls;
|
||||
}
|
||||
|
||||
public boolean isOuterClass() {
|
||||
return isOuterClass;
|
||||
public FieldReplaceAttr(InsnArg reg) {
|
||||
this.replaceType = ReplaceWith.VAR;
|
||||
this.replaceObj = reg;
|
||||
}
|
||||
|
||||
public ReplaceWith getReplaceType() {
|
||||
return replaceType;
|
||||
}
|
||||
|
||||
public ClassInfo getClsRef() {
|
||||
return (ClassInfo) replaceObj;
|
||||
}
|
||||
|
||||
public InsnArg getVarRef() {
|
||||
return (InsnArg) replaceObj;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -29,6 +44,6 @@ public class FieldReplaceAttr implements IAttribute {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "REPLACE: " + fieldInfo;
|
||||
return "REPLACE: " + replaceType + " " + replaceObj;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
package jadx.core.dex.attributes.nodes;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.IAttribute;
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class IgnoreEdgeAttr implements IAttribute {
|
||||
|
||||
private final Set<BlockNode> blocks = new HashSet<BlockNode>(3);
|
||||
private final Set<BlockNode> blocks = new HashSet<>(3);
|
||||
|
||||
public Set<BlockNode> getBlocks() {
|
||||
return blocks;
|
||||
|
||||
+17
-13
@@ -1,33 +1,38 @@
|
||||
package jadx.core.dex.attributes.nodes;
|
||||
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.IAttribute;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
public class JadxErrorAttr implements IAttribute {
|
||||
public class JadxError {
|
||||
|
||||
private final String error;
|
||||
private final Throwable cause;
|
||||
|
||||
public JadxErrorAttr(Throwable cause) {
|
||||
public JadxError(Throwable cause) {
|
||||
this(null, cause);
|
||||
}
|
||||
|
||||
public JadxError(String error, Throwable cause) {
|
||||
this.error = error;
|
||||
this.cause = cause;
|
||||
}
|
||||
|
||||
public String getError() {
|
||||
return error;
|
||||
}
|
||||
|
||||
public Throwable getCause() {
|
||||
return cause;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AType<JadxErrorAttr> getType() {
|
||||
return AType.JADX_ERROR;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder str = new StringBuilder();
|
||||
str.append("JadxError: ");
|
||||
if (cause == null) {
|
||||
str.append("null");
|
||||
} else {
|
||||
if (error != null) {
|
||||
str.append(error);
|
||||
str.append(' ');
|
||||
}
|
||||
if (cause != null) {
|
||||
str.append(cause.getClass());
|
||||
str.append(":");
|
||||
str.append(cause.getMessage());
|
||||
@@ -36,5 +41,4 @@ public class JadxErrorAttr implements IAttribute {
|
||||
}
|
||||
return str.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package jadx.core.dex.attributes.nodes;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class JadxWarn {
|
||||
|
||||
private final String warn;
|
||||
|
||||
public JadxWarn(String warn) {
|
||||
this.warn = Objects.requireNonNull(warn);
|
||||
}
|
||||
|
||||
public String getWarn() {
|
||||
return warn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "JadxWarn: " + warn;
|
||||
}
|
||||
}
|
||||
@@ -23,4 +23,9 @@ public abstract class LineAttrNode extends AttrNode {
|
||||
public void setDecompiledLine(int decompiledLine) {
|
||||
this.decompiledLine = decompiledLine;
|
||||
}
|
||||
|
||||
public void copyLines(LineAttrNode lineAttrNode) {
|
||||
setSourceLine(lineAttrNode.getSourceLine());
|
||||
setDecompiledLine(lineAttrNode.getDecompiledLine());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
package jadx.core.dex.attributes.nodes;
|
||||
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.dex.nodes.Edge;
|
||||
import jadx.core.utils.BlockUtils;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.dex.nodes.Edge;
|
||||
import jadx.core.utils.BlockUtils;
|
||||
|
||||
public class LoopInfo {
|
||||
|
||||
private final BlockNode start;
|
||||
@@ -43,7 +43,7 @@ public class LoopInfo {
|
||||
* Exit nodes belongs to loop (contains in {@code loopBlocks})
|
||||
*/
|
||||
public Set<BlockNode> getExitNodes() {
|
||||
Set<BlockNode> nodes = new HashSet<BlockNode>();
|
||||
Set<BlockNode> nodes = new HashSet<>();
|
||||
Set<BlockNode> blocks = getLoopBlocks();
|
||||
for (BlockNode block : blocks) {
|
||||
// exit: successor node not from this loop, (don't change to getCleanSuccessors)
|
||||
@@ -60,7 +60,7 @@ public class LoopInfo {
|
||||
* Return loop exit edges.
|
||||
*/
|
||||
public List<Edge> getExitEdges() {
|
||||
List<Edge> edges = new LinkedList<Edge>();
|
||||
List<Edge> edges = new LinkedList<>();
|
||||
Set<BlockNode> blocks = getLoopBlocks();
|
||||
for (BlockNode block : blocks) {
|
||||
for (BlockNode s : block.getSuccessors()) {
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
package jadx.core.dex.attributes.nodes;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.IAttribute;
|
||||
import jadx.core.dex.instructions.PhiInsn;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public class PhiListAttr implements IAttribute {
|
||||
|
||||
private final List<PhiInsn> list = new LinkedList<PhiInsn>();
|
||||
private final List<PhiInsn> list = new LinkedList<>();
|
||||
|
||||
@Override
|
||||
public AType<PhiListAttr> getType() {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package jadx.core.dex.info;
|
||||
|
||||
import jadx.core.Consts;
|
||||
|
||||
import com.android.dx.rop.code.AccessFlags;
|
||||
|
||||
import jadx.core.Consts;
|
||||
|
||||
public class AccessInfo {
|
||||
|
||||
private final int accFlags;
|
||||
@@ -140,10 +140,8 @@ public class AccessInfo {
|
||||
if (isBridge()) {
|
||||
code.append("/* bridge */ ");
|
||||
}
|
||||
if (Consts.DEBUG) {
|
||||
if (isVarArgs()) {
|
||||
code.append("/* varargs */ ");
|
||||
}
|
||||
if (Consts.DEBUG && isVarArgs()) {
|
||||
code.append("/* varargs */ ");
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
package jadx.core.dex.info;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.DexNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public final class ClassInfo {
|
||||
|
||||
private final ArgType type;
|
||||
@@ -17,50 +18,50 @@ public final class ClassInfo {
|
||||
// class info after rename (deobfuscation)
|
||||
private ClassInfo alias;
|
||||
|
||||
private ClassInfo(DexNode dex, ArgType type) {
|
||||
this(dex, type, true);
|
||||
private ClassInfo(RootNode root, ArgType type) {
|
||||
this(root, type, true);
|
||||
}
|
||||
|
||||
private ClassInfo(DexNode dex, ArgType type, boolean inner) {
|
||||
private ClassInfo(RootNode root, ArgType type, boolean inner) {
|
||||
if (!type.isObject() || type.isGeneric()) {
|
||||
throw new JadxRuntimeException("Not class type: " + type);
|
||||
}
|
||||
this.type = type;
|
||||
this.alias = this;
|
||||
|
||||
splitNames(dex, inner);
|
||||
splitNames(root, inner);
|
||||
}
|
||||
|
||||
public static ClassInfo fromType(DexNode dex, ArgType type) {
|
||||
public static ClassInfo fromType(RootNode root, ArgType type) {
|
||||
if (type.isArray()) {
|
||||
type = ArgType.OBJECT;
|
||||
}
|
||||
ClassInfo cls = dex.getInfoStorage().getCls(type);
|
||||
ClassInfo cls = root.getInfoStorage().getCls(type);
|
||||
if (cls != null) {
|
||||
return cls;
|
||||
}
|
||||
cls = new ClassInfo(dex, type);
|
||||
return dex.getInfoStorage().putCls(cls);
|
||||
cls = new ClassInfo(root, type);
|
||||
return root.getInfoStorage().putCls(cls);
|
||||
}
|
||||
|
||||
public static ClassInfo fromDex(DexNode dex, int clsIndex) {
|
||||
if (clsIndex == DexNode.NO_INDEX) {
|
||||
return null;
|
||||
}
|
||||
return fromType(dex, dex.getType(clsIndex));
|
||||
return fromType(dex.root(), dex.getType(clsIndex));
|
||||
}
|
||||
|
||||
public static ClassInfo fromName(DexNode dex, String clsName) {
|
||||
return fromType(dex, ArgType.object(clsName));
|
||||
public static ClassInfo fromName(RootNode root, String clsName) {
|
||||
return fromType(root, ArgType.object(clsName));
|
||||
}
|
||||
|
||||
public static ClassInfo extCls(DexNode dex, ArgType type) {
|
||||
ClassInfo classInfo = fromName(dex, type.getObject());
|
||||
public static ClassInfo extCls(RootNode root, ArgType type) {
|
||||
ClassInfo classInfo = fromName(root, type.getObject());
|
||||
return classInfo.alias;
|
||||
}
|
||||
|
||||
public void rename(DexNode dex, String fullName) {
|
||||
ClassInfo newAlias = new ClassInfo(dex, ArgType.object(fullName), isInner());
|
||||
public void rename(RootNode root, String fullName) {
|
||||
ClassInfo newAlias = new ClassInfo(root, ArgType.object(fullName), isInner());
|
||||
if (!alias.getFullName().equals(newAlias.getFullName())) {
|
||||
this.alias = newAlias;
|
||||
}
|
||||
@@ -74,7 +75,7 @@ public final class ClassInfo {
|
||||
return alias;
|
||||
}
|
||||
|
||||
private void splitNames(DexNode dex, boolean canBeInner) {
|
||||
private void splitNames(RootNode root, boolean canBeInner) {
|
||||
String fullObjectName = type.getObject();
|
||||
String clsName;
|
||||
int dot = fullObjectName.lastIndexOf('.');
|
||||
@@ -89,7 +90,11 @@ public final class ClassInfo {
|
||||
int sep = clsName.lastIndexOf('$');
|
||||
if (canBeInner && sep > 0 && sep != clsName.length() - 1) {
|
||||
String parClsName = pkg + "." + clsName.substring(0, sep);
|
||||
parentClass = fromName(dex, parClsName);
|
||||
if (pkg.isEmpty()) {
|
||||
parClsName = clsName.substring(0, sep);
|
||||
}
|
||||
|
||||
parentClass = fromName(root, parClsName);
|
||||
clsName = clsName.substring(sep + 1);
|
||||
} else {
|
||||
parentClass = null;
|
||||
@@ -107,10 +112,10 @@ public final class ClassInfo {
|
||||
}
|
||||
|
||||
public String getFullPath() {
|
||||
ClassInfo alias = getAlias();
|
||||
return alias.getPackage().replace('.', File.separatorChar)
|
||||
ClassInfo usedAlias = getAlias();
|
||||
return usedAlias.getPackage().replace('.', File.separatorChar)
|
||||
+ File.separatorChar
|
||||
+ alias.getNameWithoutPackage().replace('.', '_');
|
||||
+ usedAlias.getNameWithoutPackage().replace('.', '_');
|
||||
}
|
||||
|
||||
public String getFullName() {
|
||||
@@ -125,6 +130,10 @@ public final class ClassInfo {
|
||||
return pkg;
|
||||
}
|
||||
|
||||
public boolean isDefaultPackage() {
|
||||
return pkg.isEmpty();
|
||||
}
|
||||
|
||||
public String getRawName() {
|
||||
return type.getObject();
|
||||
}
|
||||
@@ -152,8 +161,8 @@ public final class ClassInfo {
|
||||
return parentClass != null;
|
||||
}
|
||||
|
||||
public void notInner(DexNode dex) {
|
||||
splitNames(dex, false);
|
||||
public void notInner(RootNode root) {
|
||||
splitNames(root, false);
|
||||
}
|
||||
|
||||
public ArgType getType() {
|
||||
@@ -167,7 +176,7 @@ public final class ClassInfo {
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return fullName.hashCode();
|
||||
return type.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
package jadx.core.dex.info;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.instructions.args.LiteralArg;
|
||||
import jadx.core.dex.instructions.args.PrimitiveType;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.DexNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.ResRefField;
|
||||
import jadx.core.dex.nodes.parser.FieldInitAttr;
|
||||
|
||||
public class ConstStorage {
|
||||
|
||||
private static final class ValueStorage {
|
||||
private final Map<Object, FieldNode> values = new HashMap<>();
|
||||
private final Set<Object> duplicates = new HashSet<>();
|
||||
|
||||
public Map<Object, FieldNode> getValues() {
|
||||
return values;
|
||||
}
|
||||
|
||||
public FieldNode get(Object key) {
|
||||
return values.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if this value is duplicated
|
||||
*/
|
||||
public boolean put(Object value, FieldNode fld) {
|
||||
FieldNode prev = values.put(value, fld);
|
||||
if (prev != null) {
|
||||
values.remove(value);
|
||||
duplicates.add(value);
|
||||
return true;
|
||||
}
|
||||
if (duplicates.contains(value)) {
|
||||
values.remove(value);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean contains(Object value) {
|
||||
return duplicates.contains(value) || values.containsKey(value);
|
||||
}
|
||||
}
|
||||
|
||||
private final boolean replaceEnabled;
|
||||
private final ValueStorage globalValues = new ValueStorage();
|
||||
private final Map<ClassNode, ValueStorage> classes = new HashMap<>();
|
||||
|
||||
private Map<Integer, String> resourcesNames = new HashMap<>();
|
||||
|
||||
public ConstStorage(JadxArgs args) {
|
||||
this.replaceEnabled = args.isReplaceConsts();
|
||||
}
|
||||
|
||||
public void processConstFields(ClassNode cls, List<FieldNode> staticFields) {
|
||||
if (!replaceEnabled || staticFields.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
for (FieldNode f : staticFields) {
|
||||
AccessInfo accFlags = f.getAccessFlags();
|
||||
if (accFlags.isStatic() && accFlags.isFinal()) {
|
||||
FieldInitAttr fv = f.get(AType.FIELD_INIT);
|
||||
if (fv != null
|
||||
&& fv.getValue() != null
|
||||
&& fv.getValueType() == FieldInitAttr.InitType.CONST
|
||||
&& fv != FieldInitAttr.NULL_VALUE) {
|
||||
addConstField(cls, f, fv.getValue(), accFlags.isPublic());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addConstField(ClassNode cls, FieldNode fld, Object value, boolean isPublic) {
|
||||
if (isPublic) {
|
||||
globalValues.put(value, fld);
|
||||
} else {
|
||||
getClsValues(cls).put(value, fld);
|
||||
}
|
||||
}
|
||||
|
||||
private ValueStorage getClsValues(ClassNode cls) {
|
||||
ValueStorage classValues = classes.get(cls);
|
||||
if (classValues == null) {
|
||||
classValues = new ValueStorage();
|
||||
classes.put(cls, classValues);
|
||||
}
|
||||
return classValues;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public FieldNode getConstField(ClassNode cls, Object value, boolean searchGlobal) {
|
||||
DexNode dex = cls.dex();
|
||||
if (value instanceof Integer) {
|
||||
String str = resourcesNames.get(value);
|
||||
if (str != null) {
|
||||
return new ResRefField(dex, str.replace('/', '.'));
|
||||
}
|
||||
}
|
||||
if (!replaceEnabled) {
|
||||
return null;
|
||||
}
|
||||
boolean foundInGlobal = globalValues.contains(value);
|
||||
if (foundInGlobal && !searchGlobal) {
|
||||
return null;
|
||||
}
|
||||
ClassNode current = cls;
|
||||
while (current != null) {
|
||||
ValueStorage classValues = classes.get(current);
|
||||
if (classValues != null) {
|
||||
FieldNode field = classValues.get(value);
|
||||
if (field != null) {
|
||||
if (foundInGlobal) {
|
||||
return null;
|
||||
}
|
||||
return field;
|
||||
}
|
||||
}
|
||||
ClassInfo parentClass = current.getClassInfo().getParentClass();
|
||||
if (parentClass == null) {
|
||||
break;
|
||||
}
|
||||
current = dex.resolveClass(parentClass);
|
||||
}
|
||||
if (searchGlobal) {
|
||||
return globalValues.get(value);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public FieldNode getConstFieldByLiteralArg(ClassNode cls, LiteralArg arg) {
|
||||
PrimitiveType type = arg.getType().getPrimitiveType();
|
||||
if (type == null) {
|
||||
return null;
|
||||
}
|
||||
long literal = arg.getLiteral();
|
||||
switch (type) {
|
||||
case BOOLEAN:
|
||||
return getConstField(cls, literal == 1, false);
|
||||
case CHAR:
|
||||
return getConstField(cls, (char) literal, Math.abs(literal) > 10);
|
||||
case BYTE:
|
||||
return getConstField(cls, (byte) literal, Math.abs(literal) > 10);
|
||||
case SHORT:
|
||||
return getConstField(cls, (short) literal, Math.abs(literal) > 100);
|
||||
case INT:
|
||||
return getConstField(cls, (int) literal, Math.abs(literal) > 100);
|
||||
case LONG:
|
||||
return getConstField(cls, literal, Math.abs(literal) > 1000);
|
||||
case FLOAT:
|
||||
float f = Float.intBitsToFloat((int) literal);
|
||||
return getConstField(cls, f, Float.compare(f, 0) == 0);
|
||||
case DOUBLE:
|
||||
double d = Double.longBitsToDouble(literal);
|
||||
return getConstField(cls, d, Double.compare(d, 0) == 0);
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void setResourcesNames(Map<Integer, String> resourcesNames) {
|
||||
this.resourcesNames = resourcesNames;
|
||||
}
|
||||
|
||||
public Map<Integer, String> getResourcesNames() {
|
||||
return resourcesNames;
|
||||
}
|
||||
|
||||
public Map<Object, FieldNode> getGlobalConstFields() {
|
||||
return globalValues.getValues();
|
||||
}
|
||||
|
||||
public boolean isReplaceEnabled() {
|
||||
return replaceEnabled;
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
package jadx.core.dex.info;
|
||||
|
||||
import com.android.dex.FieldId;
|
||||
|
||||
import jadx.core.codegen.TypeGen;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.DexNode;
|
||||
|
||||
import com.android.dex.FieldId;
|
||||
|
||||
public final class FieldInfo {
|
||||
|
||||
private final ClassInfo declClass;
|
||||
@@ -22,7 +22,7 @@ public final class FieldInfo {
|
||||
|
||||
public static FieldInfo from(DexNode dex, ClassInfo declClass, String name, ArgType type) {
|
||||
FieldInfo field = new FieldInfo(declClass, name, type);
|
||||
return dex.getInfoStorage().getField(field);
|
||||
return dex.root().getInfoStorage().getField(field);
|
||||
}
|
||||
|
||||
public static FieldInfo fromDex(DexNode dex, int index) {
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
package jadx.core.dex.info;
|
||||
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.DexNode;
|
||||
|
||||
public class InfoStorage {
|
||||
|
||||
private final Map<ArgType, ClassInfo> classes = new HashMap<ArgType, ClassInfo>();
|
||||
private final Map<Integer, MethodInfo> methods = new HashMap<Integer, MethodInfo>();
|
||||
private final Map<FieldInfo, FieldInfo> fields = new HashMap<FieldInfo, FieldInfo>();
|
||||
private final Map<ArgType, ClassInfo> classes = new HashMap<>();
|
||||
private final Map<Integer, MethodInfo> methods = new HashMap<>();
|
||||
private final Map<FieldInfo, FieldInfo> fields = new HashMap<>();
|
||||
|
||||
public ClassInfo getCls(ArgType type) {
|
||||
return classes.get(type);
|
||||
@@ -22,13 +23,17 @@ public class InfoStorage {
|
||||
}
|
||||
}
|
||||
|
||||
public MethodInfo getMethod(int mtdId) {
|
||||
return methods.get(mtdId);
|
||||
private int generateMethodLookupId(DexNode dex, int mthId) {
|
||||
return dex.getDexId() << 16 | mthId;
|
||||
}
|
||||
|
||||
public MethodInfo putMethod(int mthId, MethodInfo mth) {
|
||||
public MethodInfo getMethod(DexNode dex, int mtdId) {
|
||||
return methods.get(generateMethodLookupId(dex, mtdId));
|
||||
}
|
||||
|
||||
public MethodInfo putMethod(DexNode dex, int mthId, MethodInfo mth) {
|
||||
synchronized (methods) {
|
||||
MethodInfo prev = methods.put(mthId, mth);
|
||||
MethodInfo prev = methods.put(generateMethodLookupId(dex, mthId), mth);
|
||||
return prev == null ? mth : prev;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
package jadx.core.dex.info;
|
||||
|
||||
import jadx.core.codegen.TypeGen;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.DexNode;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.android.dex.MethodId;
|
||||
import com.android.dex.ProtoId;
|
||||
|
||||
import jadx.core.codegen.TypeGen;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.DexNode;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
public final class MethodInfo {
|
||||
|
||||
private final String name;
|
||||
@@ -18,11 +18,13 @@ public final class MethodInfo {
|
||||
private final ClassInfo declClass;
|
||||
private final String shortId;
|
||||
private String alias;
|
||||
private boolean aliasFromPreset;
|
||||
|
||||
private MethodInfo(DexNode dex, int mthIndex) {
|
||||
MethodId mthId = dex.getMethodId(mthIndex);
|
||||
name = dex.getString(mthId.getNameIndex());
|
||||
alias = name;
|
||||
aliasFromPreset = false;
|
||||
declClass = ClassInfo.fromDex(dex, mthId.getDeclaringClassIndex());
|
||||
|
||||
ProtoId proto = dex.getProtoId(mthId.getProtoIndex());
|
||||
@@ -32,12 +34,12 @@ public final class MethodInfo {
|
||||
}
|
||||
|
||||
public static MethodInfo fromDex(DexNode dex, int mthIndex) {
|
||||
MethodInfo mth = dex.getInfoStorage().getMethod(mthIndex);
|
||||
MethodInfo mth = dex.root().getInfoStorage().getMethod(dex, mthIndex);
|
||||
if (mth != null) {
|
||||
return mth;
|
||||
}
|
||||
mth = new MethodInfo(dex, mthIndex);
|
||||
return dex.getInfoStorage().putMethod(mthIndex, mth);
|
||||
return dex.root().getInfoStorage().putMethod(dex, mthIndex, mth);
|
||||
}
|
||||
|
||||
public String makeSignature(boolean includeRetType) {
|
||||
@@ -109,6 +111,14 @@ public final class MethodInfo {
|
||||
return !name.equals(alias);
|
||||
}
|
||||
|
||||
public boolean isAliasFromPreset() {
|
||||
return aliasFromPreset;
|
||||
}
|
||||
|
||||
public void setAliasFromPreset(boolean value) {
|
||||
aliasFromPreset = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = declClass.hashCode();
|
||||
@@ -122,10 +132,7 @@ public final class MethodInfo {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
if (!(obj instanceof MethodInfo)) {
|
||||
return false;
|
||||
}
|
||||
MethodInfo other = (MethodInfo) obj;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package jadx.core.dex.instructions;
|
||||
|
||||
import com.android.dx.io.instructions.DecodedInstruction;
|
||||
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
@@ -7,8 +9,6 @@ import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.utils.InsnUtils;
|
||||
|
||||
import com.android.dx.io.instructions.DecodedInstruction;
|
||||
|
||||
public class ArithNode extends InsnNode {
|
||||
|
||||
private final ArithOp op;
|
||||
@@ -40,7 +40,6 @@ public class ArithNode extends InsnNode {
|
||||
addReg(insn, 2, type);
|
||||
}
|
||||
}
|
||||
assert getArgsCount() == 2;
|
||||
}
|
||||
|
||||
public ArithNode(ArithOp op, RegisterArg res, InsnArg a, InsnArg b) {
|
||||
|
||||
@@ -16,6 +16,11 @@ public final class ConstClassNode extends InsnNode {
|
||||
return clsType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InsnNode copy() {
|
||||
return copyCommonParams(new ConstClassNode(clsType));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSame(InsnNode obj) {
|
||||
if (this == obj) {
|
||||
|
||||
@@ -15,6 +15,11 @@ public final class ConstStringNode extends InsnNode {
|
||||
return str;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InsnNode copy() {
|
||||
return copyCommonParams(new ConstStringNode(str));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSame(InsnNode obj) {
|
||||
if (this == obj) {
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
package jadx.core.dex.instructions;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.android.dx.io.instructions.FillArrayDataPayloadDecodedInstruction;
|
||||
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.LiteralArg;
|
||||
@@ -8,11 +13,6 @@ import jadx.core.dex.nodes.DexNode;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.android.dx.io.instructions.FillArrayDataPayloadDecodedInstruction;
|
||||
|
||||
public final class FillArrayNode extends InsnNode {
|
||||
|
||||
private final Object data;
|
||||
@@ -66,7 +66,7 @@ public final class FillArrayNode extends InsnNode {
|
||||
}
|
||||
|
||||
public List<LiteralArg> getLiteralArgs() {
|
||||
List<LiteralArg> list = new ArrayList<LiteralArg>(size);
|
||||
List<LiteralArg> list = new ArrayList<>(size);
|
||||
Object array = data;
|
||||
if (array instanceof int[]) {
|
||||
for (int b : (int[]) array) {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package jadx.core.dex.instructions;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class FilledNewArrayNode extends InsnNode {
|
||||
|
||||
private final ArgType elemType;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package jadx.core.dex.instructions;
|
||||
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.utils.InsnUtils;
|
||||
|
||||
public class GotoNode extends InsnNode {
|
||||
public class GotoNode extends TargetInsnNode {
|
||||
|
||||
protected int target;
|
||||
protected final int target;
|
||||
|
||||
public GotoNode(int target) {
|
||||
this(InsnType.GOTO, target, 0);
|
||||
@@ -20,6 +20,15 @@ public class GotoNode extends InsnNode {
|
||||
return target;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean replaceTargetBlock(BlockNode origin, BlockNode replace) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initBlocks(BlockNode curBlock) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString() + "-> " + InsnUtils.formatOffset(target);
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
package jadx.core.dex.instructions;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.android.dx.io.instructions.DecodedInstruction;
|
||||
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.PrimitiveType;
|
||||
@@ -7,16 +11,16 @@ import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.utils.InsnUtils;
|
||||
|
||||
import com.android.dx.io.instructions.DecodedInstruction;
|
||||
|
||||
import static jadx.core.utils.BlockUtils.getBlockByOffset;
|
||||
import static jadx.core.utils.BlockUtils.selectOther;
|
||||
|
||||
public class IfNode extends GotoNode {
|
||||
|
||||
// change default types priority
|
||||
private static final ArgType ARG_TYPE = ArgType.unknown(
|
||||
PrimitiveType.INT, PrimitiveType.OBJECT, PrimitiveType.ARRAY,
|
||||
PrimitiveType.BOOLEAN, PrimitiveType.SHORT, PrimitiveType.CHAR);
|
||||
PrimitiveType.INT,
|
||||
PrimitiveType.OBJECT, PrimitiveType.ARRAY,
|
||||
PrimitiveType.BOOLEAN, PrimitiveType.BYTE, PrimitiveType.SHORT, PrimitiveType.CHAR);
|
||||
|
||||
protected IfOp op;
|
||||
|
||||
@@ -45,7 +49,6 @@ public class IfNode extends GotoNode {
|
||||
BlockNode tmp = thenBlock;
|
||||
thenBlock = elseBlock;
|
||||
elseBlock = tmp;
|
||||
target = thenBlock.getStartOffset();
|
||||
}
|
||||
|
||||
public void changeCondition(IfOp op, InsnArg arg1, InsnArg arg2) {
|
||||
@@ -54,15 +57,31 @@ public class IfNode extends GotoNode {
|
||||
setArg(1, arg2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initBlocks(BlockNode curBlock) {
|
||||
thenBlock = getBlockByOffset(target, curBlock.getSuccessors());
|
||||
if (curBlock.getSuccessors().size() == 1) {
|
||||
List<BlockNode> successors = curBlock.getSuccessors();
|
||||
thenBlock = getBlockByOffset(target, successors);
|
||||
if (successors.size() == 1) {
|
||||
elseBlock = thenBlock;
|
||||
} else {
|
||||
elseBlock = selectOther(thenBlock, curBlock.getSuccessors());
|
||||
elseBlock = selectOther(thenBlock, successors);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean replaceTargetBlock(BlockNode origin, BlockNode replace) {
|
||||
boolean replaced = false;
|
||||
if (thenBlock == origin) {
|
||||
thenBlock = replace;
|
||||
replaced = true;
|
||||
}
|
||||
if (elseBlock == origin) {
|
||||
elseBlock = replace;
|
||||
replaced = true;
|
||||
}
|
||||
return replaced;
|
||||
}
|
||||
|
||||
public BlockNode getThenBlock() {
|
||||
return thenBlock;
|
||||
}
|
||||
@@ -71,6 +90,11 @@ public class IfNode extends GotoNode {
|
||||
return elseBlock;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTarget() {
|
||||
return thenBlock == null ? target : thenBlock.getStartOffset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSame(InsnNode obj) {
|
||||
if (this == obj) {
|
||||
|
||||
@@ -16,6 +16,11 @@ public class IndexInsnNode extends InsnNode {
|
||||
return index;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IndexInsnNode copy() {
|
||||
return copyCommonParams(new IndexInsnNode(insnType, index, getArgsCount()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSame(InsnNode obj) {
|
||||
if (this == obj) {
|
||||
|
||||
@@ -1,5 +1,24 @@
|
||||
package jadx.core.dex.instructions;
|
||||
|
||||
import java.io.EOFException;
|
||||
|
||||
import com.android.dex.ClassData;
|
||||
import com.android.dex.Code;
|
||||
import com.android.dex.FieldId;
|
||||
import com.android.dx.io.OpcodeInfo;
|
||||
import com.android.dx.io.Opcodes;
|
||||
import com.android.dx.io.instructions.DecodedInstruction;
|
||||
import com.android.dx.io.instructions.FillArrayDataPayloadDecodedInstruction;
|
||||
import com.android.dx.io.instructions.PackedSwitchPayloadDecodedInstruction;
|
||||
import com.android.dx.io.instructions.ShortArrayCodeInput;
|
||||
import com.android.dx.io.instructions.SparseSwitchPayloadDecodedInstruction;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.core.dex.info.FieldInfo;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
@@ -12,16 +31,8 @@ import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.utils.InsnUtils;
|
||||
import jadx.core.utils.exceptions.DecodeException;
|
||||
|
||||
import com.android.dex.Code;
|
||||
import com.android.dx.io.OpcodeInfo;
|
||||
import com.android.dx.io.Opcodes;
|
||||
import com.android.dx.io.instructions.DecodedInstruction;
|
||||
import com.android.dx.io.instructions.FillArrayDataPayloadDecodedInstruction;
|
||||
import com.android.dx.io.instructions.PackedSwitchPayloadDecodedInstruction;
|
||||
import com.android.dx.io.instructions.ShortArrayCodeInput;
|
||||
import com.android.dx.io.instructions.SparseSwitchPayloadDecodedInstruction;
|
||||
|
||||
public class InsnDecoder {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(InsnDecoder.class);
|
||||
|
||||
private final MethodNode method;
|
||||
private final DexNode dex;
|
||||
@@ -37,26 +48,36 @@ public class InsnDecoder {
|
||||
int size = encodedInstructions.length;
|
||||
DecodedInstruction[] decoded = new DecodedInstruction[size];
|
||||
ShortArrayCodeInput in = new ShortArrayCodeInput(encodedInstructions);
|
||||
|
||||
try {
|
||||
while (in.hasMore()) {
|
||||
decoded[in.cursor()] = DecodedInstruction.decode(in);
|
||||
decoded[in.cursor()] = decodeRawInsn(in);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new DecodeException(method, "", e);
|
||||
throw new DecodeException(method, e.getMessage(), e);
|
||||
}
|
||||
insnArr = decoded;
|
||||
}
|
||||
|
||||
private DecodedInstruction decodeRawInsn(ShortArrayCodeInput in) throws EOFException {
|
||||
int opcodeUnit = in.read();
|
||||
int opcode = Opcodes.extractOpcodeFromUnit(opcodeUnit);
|
||||
OpcodeInfo.Info opcodeInfo;
|
||||
try {
|
||||
opcodeInfo = OpcodeInfo.get(opcode);
|
||||
} catch (IllegalArgumentException e) {
|
||||
LOG.warn("Ignore decode error: '{}', replace with NOP instruction", e.getMessage());
|
||||
opcodeInfo = OpcodeInfo.NOP;
|
||||
}
|
||||
return opcodeInfo.getFormat().decode(opcodeUnit, in);
|
||||
}
|
||||
|
||||
public InsnNode[] process() throws DecodeException {
|
||||
InsnNode[] instructions = new InsnNode[insnArr.length];
|
||||
for (int i = 0; i < insnArr.length; i++) {
|
||||
DecodedInstruction rawInsn = insnArr[i];
|
||||
if (rawInsn != null) {
|
||||
InsnNode insn = decode(rawInsn, i);
|
||||
if (insn != null) {
|
||||
insn.setOffset(i);
|
||||
}
|
||||
insn.setOffset(i);
|
||||
instructions[i] = insn;
|
||||
} else {
|
||||
instructions[i] = null;
|
||||
@@ -66,13 +87,14 @@ public class InsnDecoder {
|
||||
return instructions;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private InsnNode decode(DecodedInstruction insn, int offset) throws DecodeException {
|
||||
switch (insn.getOpcode()) {
|
||||
case Opcodes.NOP:
|
||||
case Opcodes.PACKED_SWITCH_PAYLOAD:
|
||||
case Opcodes.SPARSE_SWITCH_PAYLOAD:
|
||||
case Opcodes.FILL_ARRAY_DATA_PAYLOAD:
|
||||
return null;
|
||||
return new InsnNode(InsnType.NOP, 0);
|
||||
|
||||
// move-result will be process in invoke and filled-new-array instructions
|
||||
case Opcodes.MOVE_RESULT:
|
||||
@@ -307,6 +329,11 @@ public class InsnDecoder {
|
||||
case Opcodes.NEG_DOUBLE:
|
||||
return neg(insn, ArgType.DOUBLE);
|
||||
|
||||
case Opcodes.NOT_INT:
|
||||
return not(insn, ArgType.INT);
|
||||
case Opcodes.NOT_LONG:
|
||||
return not(insn, ArgType.LONG);
|
||||
|
||||
case Opcodes.INT_TO_BYTE:
|
||||
return cast(insn, ArgType.INT, ArgType.BYTE);
|
||||
case Opcodes.INT_TO_CHAR:
|
||||
@@ -563,9 +590,10 @@ public class InsnDecoder {
|
||||
return insn(InsnType.MONITOR_EXIT,
|
||||
null,
|
||||
InsnArg.reg(insn, 0, ArgType.UNKNOWN_OBJECT));
|
||||
}
|
||||
|
||||
throw new DecodeException("Unknown instruction: " + OpcodeInfo.getName(insn.getOpcode()));
|
||||
default:
|
||||
throw new DecodeException("Unknown instruction: '" + OpcodeInfo.getName(insn.getOpcode()) + "'");
|
||||
}
|
||||
}
|
||||
|
||||
private InsnNode decodeSwitch(DecodedInstruction insn, int offset, boolean packed) {
|
||||
@@ -681,6 +709,13 @@ public class InsnDecoder {
|
||||
return inode;
|
||||
}
|
||||
|
||||
private InsnNode not(DecodedInstruction insn, ArgType type) {
|
||||
InsnNode inode = new InsnNode(InsnType.NOT, 1);
|
||||
inode.setResult(InsnArg.reg(insn, 0, type));
|
||||
inode.addArg(InsnArg.reg(insn, 1, type));
|
||||
return inode;
|
||||
}
|
||||
|
||||
private InsnNode insn(InsnType type, RegisterArg res) {
|
||||
InsnNode node = new InsnNode(type, 0);
|
||||
node.setResult(res);
|
||||
|
||||
@@ -8,6 +8,7 @@ public enum InsnType {
|
||||
|
||||
ARITH,
|
||||
NEG,
|
||||
NOT,
|
||||
|
||||
MOVE,
|
||||
CAST,
|
||||
@@ -65,6 +66,9 @@ public enum InsnType {
|
||||
ONE_ARG,
|
||||
PHI,
|
||||
|
||||
// merge all arguments in one
|
||||
MERGE,
|
||||
|
||||
// TODO: now multidimensional arrays created using Array.newInstance function
|
||||
NEW_MULTIDIM_ARRAY
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package jadx.core.dex.instructions;
|
||||
|
||||
import com.android.dx.io.instructions.DecodedInstruction;
|
||||
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
@@ -7,8 +9,6 @@ import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.utils.InsnUtils;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
import com.android.dx.io.instructions.DecodedInstruction;
|
||||
|
||||
public class InvokeNode extends InsnNode {
|
||||
|
||||
private final InvokeType type;
|
||||
@@ -36,6 +36,12 @@ public class InvokeNode extends InsnNode {
|
||||
}
|
||||
}
|
||||
|
||||
private InvokeNode(MethodInfo mth, InvokeType invokeType, int argsCount) {
|
||||
super(InsnType.INVOKE, argsCount);
|
||||
this.mth = mth;
|
||||
this.type = invokeType;
|
||||
}
|
||||
|
||||
public InvokeType getInvokeType() {
|
||||
return type;
|
||||
}
|
||||
@@ -44,6 +50,11 @@ public class InvokeNode extends InsnNode {
|
||||
return mth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InsnNode copy() {
|
||||
return copyCommonParams(new InvokeNode(mth, type, getArgsCount()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSame(InsnNode obj) {
|
||||
if (this == obj) {
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
package jadx.core.dex.instructions;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class NewArrayNode extends InsnNode {
|
||||
|
||||
private final ArgType arrType;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user