Compare commits
311 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f02a33ace3 | |||
| 9c34a3154d | |||
| ed385e8cf1 | |||
| 554e119eb9 | |||
| aad70c7199 | |||
| a051ce6cf4 | |||
| 40f19cce61 | |||
| b158858349 | |||
| d6737860bb | |||
| 123ba2baf1 | |||
| f2f8936cd1 | |||
| f0f5c26896 | |||
| 6c61ce52a3 | |||
| 1830c273c0 | |||
| 5efe4bd845 | |||
| 75a6714057 | |||
| 6339cc2088 | |||
| 98e4c4b48d | |||
| 9d5dda12be | |||
| 84b9f11120 | |||
| 2383c40105 | |||
| 305cf5379d | |||
| 9189f23e3e | |||
| 628263343b | |||
| 19cf7c9f14 | |||
| 363cd85ba6 | |||
| 7bb752715f | |||
| 9622c948c9 | |||
| baea5247f4 | |||
| 0ca2789a18 | |||
| 119709b844 | |||
| 1c914ff286 | |||
| 31a02a70a0 | |||
| 8e0df4c423 | |||
| 86a4ed7fb3 | |||
| 19c57258fe | |||
| fef3e55c55 | |||
| 6f973ca2af | |||
| 4b73d24d4b | |||
| 65818dccb1 | |||
| 7ac0b9f57c | |||
| 699f7f6716 | |||
| dae882d55c | |||
| c0a0bba5d8 | |||
| 52ba33c5a3 | |||
| 156c979842 | |||
| f846df5371 | |||
| 4a39af7cb3 | |||
| c7890f2468 | |||
| e1dfb4ee59 | |||
| 031582dd55 | |||
| 745c52e8db | |||
| cab3f5daa7 | |||
| 77cee15d64 | |||
| e7e7b664dd | |||
| db7f2cf548 | |||
| 58365a8907 | |||
| 172f7f7534 | |||
| 05e5c82c9b | |||
| 30fbf4bcfa | |||
| 9645f33c7b | |||
| 336d6ce189 | |||
| f283ef4342 | |||
| 41abbb12a0 | |||
| 89b80900f0 | |||
| f1539d2e37 | |||
| 84ef6d0049 | |||
| aa41a4d93b | |||
| 616752759b | |||
| dc004f37ee | |||
| cfbbd99bb8 | |||
| c74b7f20a5 | |||
| 9d22b3caa8 | |||
| f8039733cc | |||
| 87ca14afea | |||
| c134329ce9 | |||
| 2148d4b0f5 | |||
| 632cc3ec16 | |||
| bcfed5b362 | |||
| 4cb9f23a7d | |||
| 0aa7173e83 | |||
| b1b49e6195 | |||
| d23f4ac16a | |||
| 01da127c4e | |||
| ccb9c46005 | |||
| 01dfae4ac7 | |||
| 395cae439e | |||
| eb77aa51b2 | |||
| ac1d1a5858 | |||
| 74a72a5ce0 | |||
| a1bfdc6323 | |||
| 0720992998 | |||
| ef28875a8e | |||
| 10fb57f6fb | |||
| 7186a4a2d7 | |||
| ab4721a8b3 | |||
| 23c05bb5f6 | |||
| fe41174be8 | |||
| 513766d45b | |||
| 79ccaadaff | |||
| ecaa87e7ae | |||
| 0a08d8b653 | |||
| 7b18d3a3a8 | |||
| 058e4c9fd7 | |||
| 9d257cd115 | |||
| 1e5541175e | |||
| bae7f1b09c | |||
| e6e8f6367e | |||
| 3970fce503 | |||
| eda2272430 | |||
| 207ce6cbbe | |||
| 1d3e6ecbcf | |||
| a5a951cfa1 | |||
| a6f935ed68 | |||
| b09c7ba6b8 | |||
| ec66476ac6 | |||
| 008216d599 | |||
| 4a92275adb | |||
| 6fca311de0 | |||
| 8e279f55f1 | |||
| 2caac21b73 | |||
| c5d977baca | |||
| b5344f4577 | |||
| 0fa3842a70 | |||
| 6fc7c7a462 | |||
| 98dbd48890 | |||
| 55fc498359 | |||
| ba6dd081e9 | |||
| 7cdb0318b1 | |||
| 17d8516d3b | |||
| b78349aef7 | |||
| eb141ad12b | |||
| b446bf275c | |||
| b7109b1b2b | |||
| 3537f849ef | |||
| 9557f04fe7 | |||
| 1bb53329b5 | |||
| e026345a45 | |||
| 3492ec3517 | |||
| eb2a1734d3 | |||
| aa8a7c03c3 | |||
| 36ee994eb8 | |||
| 65544c64bf | |||
| b49acfdacf | |||
| 29d3ce15a8 | |||
| 84cb6b9569 | |||
| a848eab407 | |||
| e1f4955286 | |||
| ca21ca5d81 | |||
| 2399bfb784 | |||
| 11cee083ba | |||
| 6e66dc25c8 | |||
| 999793c944 | |||
| d111fd0680 | |||
| eed762df44 | |||
| d3dbdb24af | |||
| e585c4ec46 | |||
| 66421be942 | |||
| 9695291e37 | |||
| b65c386b6a | |||
| cd6f6b7a83 | |||
| cdaecb31df | |||
| 5169dc52dd | |||
| f72abb2867 | |||
| 2c0725390e | |||
| 5a940a3baf | |||
| 16b6345c7f | |||
| 5f0dbf856b | |||
| 2e9039da4e | |||
| 650cf31562 | |||
| 42b7843761 | |||
| d5f4266283 | |||
| 988ada3ce9 | |||
| 74562e6868 | |||
| dd2e7e879b | |||
| 365c1faf25 | |||
| 9797fe5b81 | |||
| 6d052d39ad | |||
| 2b242b9109 | |||
| 1c8741332e | |||
| a3ff03c8f3 | |||
| 3019ee5655 | |||
| 03ae3bcefa | |||
| 52deb48aac | |||
| 214866fcb9 | |||
| 7654661b77 | |||
| 51a9c741a5 | |||
| bce86d3211 | |||
| a4a8b05ef0 | |||
| 6116a75022 | |||
| 7243ab5cb6 | |||
| 43538902a3 | |||
| cf79a519d3 | |||
| d069928613 | |||
| dd13edf262 | |||
| 653bb2ac10 | |||
| cbdc2496fc | |||
| 3de04cb638 | |||
| 68d074aecf | |||
| 0df5aa80fe | |||
| 28bcad202a | |||
| 16d8d41baf | |||
| 7bd175220e | |||
| 28d348b364 | |||
| 91691fbd6a | |||
| 9856b6d3c5 | |||
| e1ca290424 | |||
| 0fa19fb0ac | |||
| ab7b6fc29f | |||
| f8acc31b0b | |||
| 4197365131 | |||
| 389caf1825 | |||
| bcadc28207 | |||
| 7e95758a6e | |||
| d44dd0de84 | |||
| 5cee498e1d | |||
| db1b027da2 | |||
| 7f4e641860 | |||
| 710245d597 | |||
| b689efcc9f | |||
| 89563b624b | |||
| 8c7140d6b8 | |||
| c892395089 | |||
| 4ce5cc8492 | |||
| 9b091b7c08 | |||
| 7b14e322d3 | |||
| 21acaa8d37 | |||
| bf42b97580 | |||
| c705f8cbff | |||
| f8c0449d4e | |||
| b28eaa1a94 | |||
| be509c7104 | |||
| 2931617202 | |||
| 82d0d622a8 | |||
| bcaca781b1 | |||
| ffedaea505 | |||
| aec986447e | |||
| b0e3cfedf4 | |||
| da41efa3db | |||
| 9e0cd2e14e | |||
| d1af751226 | |||
| d8b39c2698 | |||
| 618b014b3d | |||
| 4e990ae2b0 | |||
| 41ee57a6f7 | |||
| 7c353a6c6f | |||
| 72b2663949 | |||
| 727285e3df | |||
| a932c7c569 | |||
| 1272ae2d4d | |||
| ddaf0375dc | |||
| f60bb6b121 | |||
| fd3498add6 | |||
| 43de744c88 | |||
| 1ac2cdfc41 | |||
| 2dea6f55b5 | |||
| 76cf4f053f | |||
| eadf046b2c | |||
| e9591efd7e | |||
| fbf750f588 | |||
| 63c528dba9 | |||
| 0f27eba1b1 | |||
| a841d0ebe7 | |||
| 6a1717a624 | |||
| ee6508e93c | |||
| 5ad082627f | |||
| e0624ce986 | |||
| 995cf2ad42 | |||
| b9fffa149b | |||
| 7e8435cceb | |||
| 37071dbaf3 | |||
| 87c1231422 | |||
| d553157bb3 | |||
| 95f9ab035d | |||
| 21e11c1d47 | |||
| 6d59f77165 | |||
| 3a798cb21c | |||
| 1fc92d2a16 | |||
| 850bd96976 | |||
| 20b03aa755 | |||
| 5281eed1a5 | |||
| bedbf94b4a | |||
| 47917fd5c2 | |||
| 0abb51c87a | |||
| 557667b125 | |||
| 1d7bb43dfd | |||
| 6b3e8f083c | |||
| bc629337d6 | |||
| 58993b9799 | |||
| a3464d7184 | |||
| a8a31643f1 | |||
| df9ae295db | |||
| 8c348c935c | |||
| 3815d30fc1 | |||
| 778b9bb851 | |||
| 57dd9e6146 | |||
| 8eef4a9075 | |||
| 87f50ab513 | |||
| 2de86b6db5 | |||
| 9be62fb16e | |||
| f6f883b9d1 | |||
| 5de4d0792f | |||
| 8c43e7f7ce | |||
| 9e24a5abeb | |||
| b587b6d694 | |||
| bc3af8e64d | |||
| 7bd428cf6d | |||
| 912f3c8467 | |||
| a8febb2447 | |||
| 1b0b526822 | |||
| 6250ebdd75 |
@@ -0,0 +1,20 @@
|
||||
# EditorConfig is awesome: https://EditorConfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
|
||||
indent_style = tab
|
||||
tab_width = 4
|
||||
continuation_indent_size = 8 #IntelliJ Idea specific workaround
|
||||
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.yml]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.bat]
|
||||
end_of_line = crlf
|
||||
@@ -0,0 +1,23 @@
|
||||
---
|
||||
name: Decompilation error
|
||||
about: Create a report to help us improve jadx decompiler
|
||||
title: "[core]"
|
||||
labels: Core, bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Checks before report**
|
||||
- [ ] check [latest unstable build](https://bintray.com/skylot/jadx/unstable/_latestVersion#files) (maybe issue already fixed)
|
||||
- [ ] search existing issues by exception message
|
||||
(for example `JadxRuntimeException: Can't find immediate dominator for block`)
|
||||
- [ ] check [Troubleshooting Q&A](https://github.com/skylot/jadx/wiki/Troubleshooting-Q&A) section on wiki
|
||||
|
||||
**Describe error**
|
||||
- provide full name of method or class with error
|
||||
- provide full java stacktrace
|
||||
|
||||
**Note**: no need to copy method fallback code (commented pseudocode)
|
||||
- attach or provide link to apk file (double check apk version)
|
||||
|
||||
**Note**: GitHub don't allow attach files with `.apk` extension, but you can change extension by adding `.zip` at the end :)
|
||||
@@ -9,6 +9,8 @@ out/
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.attach_pid*
|
||||
*.hprof
|
||||
|
||||
**/.DS_Store
|
||||
|
||||
@@ -28,3 +30,4 @@ jadx-output/
|
||||
*.dump
|
||||
*.log
|
||||
*.cfg
|
||||
*.orig
|
||||
|
||||
+23
-15
@@ -1,24 +1,32 @@
|
||||
image: java:8
|
||||
|
||||
variables:
|
||||
GRADLE_OPTS: "-Dorg.gradle.daemon=false"
|
||||
TERM: "dumb"
|
||||
GRADLE_OPTS: "-Dorg.gradle.daemon=false"
|
||||
TERM: "dumb"
|
||||
|
||||
before_script:
|
||||
- chmod +x gradlew
|
||||
- chmod +x gradlew
|
||||
|
||||
stages:
|
||||
- build
|
||||
- test
|
||||
- check
|
||||
|
||||
build:
|
||||
stage: build
|
||||
java-8:
|
||||
stage: test
|
||||
image: openjdk:8
|
||||
script: ./gradlew clean build
|
||||
|
||||
java-11:
|
||||
stage: test
|
||||
image: openjdk:11
|
||||
script: ./gradlew clean build
|
||||
|
||||
check:
|
||||
stage: check
|
||||
image: openjdk:8
|
||||
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
|
||||
- export JADX_LAST_TAG="$(git describe --abbrev=0 --tags)"
|
||||
- export JADX_VERSION="${JADX_LAST_TAG:1}-dev-$(git rev-parse --short HEAD)"
|
||||
- ./gradlew clean sonarqube -Dsonar.host.url=$SONAR_HOST -Dsonar.organization=$SONAR_ORG -Dsonar.login=$SONAR_TOKEN -Dsonar.branch.name=dev
|
||||
- ./gradlew clean dist
|
||||
artifacts:
|
||||
paths:
|
||||
- build/jadx*.zip
|
||||
- build/jadx*.exe
|
||||
- build/jadx*.zip
|
||||
|
||||
+21
-2
@@ -1,16 +1,35 @@
|
||||
branch: release
|
||||
verifyConditions:
|
||||
- '@semantic-release/github'
|
||||
|
||||
prepare:
|
||||
- path: '@semantic-release/exec'
|
||||
cmd: "JADX_VERSION=${nextRelease.version} ./gradlew clean dist"
|
||||
|
||||
preset: "angular"
|
||||
|
||||
generateNotes:
|
||||
- path: '@semantic-release/release-notes-generator'
|
||||
writerOpts:
|
||||
groupBy: "type"
|
||||
commitGroupsSort:
|
||||
- "feat"
|
||||
- "perf"
|
||||
- "fix"
|
||||
commitsSort: "header"
|
||||
|
||||
publish:
|
||||
- path: '@semantic-release/exec'
|
||||
cmd: "JADX_VERSION=${nextRelease.version} BINTRAY_PACKAGE=releases bash scripts/bintray-upload.sh"
|
||||
- path: '@semantic-release/github'
|
||||
assets:
|
||||
- path: 'build/*.zip'
|
||||
label: 'zip bundle'
|
||||
- path: 'build/*.exe'
|
||||
label: 'jadx-gui windows'
|
||||
|
||||
success:
|
||||
- path: '@semantic-release/github'
|
||||
successComment: false
|
||||
|
||||
fail:
|
||||
- path: '@semantic-release/github'
|
||||
failComment: false
|
||||
|
||||
+21
-31
@@ -9,42 +9,32 @@ git:
|
||||
depth: false
|
||||
|
||||
before_install:
|
||||
- wget https://github.com/sormuras/bach/raw/master/install-jdk.sh
|
||||
- chmod +x gradlew
|
||||
|
||||
# override install to skip 'gradle assemble'
|
||||
install: true
|
||||
|
||||
env:
|
||||
global:
|
||||
- TERM=dumb
|
||||
- JADX_VERSION="$(git describe --abbrev=0 --tags)-b$TRAVIS_BUILD_NUMBER-$(git rev-parse --short HEAD)"
|
||||
- JADX_LAST_TAG=$(git describe --abbrev=0 --tags)
|
||||
- JADX_VERSION="${JADX_LAST_TAG:1}-b$TRAVIS_BUILD_NUMBER-$(git rev-parse --short HEAD)"
|
||||
|
||||
matrix:
|
||||
jdk:
|
||||
- openjdk8
|
||||
- oraclejdk8
|
||||
- openjdk11
|
||||
|
||||
script: ./gradlew clean build
|
||||
|
||||
jobs:
|
||||
include:
|
||||
- env: JDK=oracle-8
|
||||
jdk: oraclejdk8
|
||||
- env: JDK=oracle-10
|
||||
install: . ./install-jdk.sh -F 10 -L BCL
|
||||
- stage: deploy-unstable
|
||||
jdk: openjdk8
|
||||
if: branch = master AND repo = env(MAIN_REPO) AND type = push
|
||||
script: bash scripts/travis-master.sh
|
||||
|
||||
script:
|
||||
- java -version
|
||||
- ./gradlew clean build
|
||||
|
||||
deploy:
|
||||
- provider: script
|
||||
skip_cleanup: true
|
||||
on:
|
||||
branch: master
|
||||
tags: false
|
||||
condition: $JDK = oracle-8
|
||||
script: bash scripts/travis-master.sh
|
||||
|
||||
- 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
|
||||
- stage: deploy-release
|
||||
jdk: openjdk8
|
||||
if: branch = release AND repo = env(MAIN_REPO) AND type = push
|
||||
script: bash scripts/travis-release.sh
|
||||
|
||||
@@ -25,8 +25,13 @@ After download unpack zip file go to `bin` directory and run:
|
||||
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:
|
||||
|
||||
### Related projects:
|
||||
- [PyJadx](https://github.com/romainthomas/pyjadx) - python binding for jadx by [@romainthomas](https://github.com/romainthomas)
|
||||
|
||||
|
||||
### Building jadx from source
|
||||
JDK 8 or higher must be installed:
|
||||
|
||||
git clone https://github.com/skylot/jadx.git
|
||||
cd jadx
|
||||
@@ -37,6 +42,10 @@ Java 8 JDK or higher must be installed:
|
||||
Scripts for run jadx will be placed in `build/jadx/bin`
|
||||
and also packed to `build/jadx-<version>.zip`
|
||||
|
||||
### macOS
|
||||
You can install using brew:
|
||||
|
||||
brew install jadx
|
||||
|
||||
### Run
|
||||
Run **jadx** on itself:
|
||||
@@ -49,31 +58,41 @@ Run **jadx** on itself:
|
||||
|
||||
### Usage
|
||||
```
|
||||
jadx[-gui] [options] <input file> (.dex, .apk, .jar or .class)
|
||||
jadx[-gui] [options] <input file> (.apk, .dex, .jar, .class, .smali, .zip, .aar, .arsc)
|
||||
options:
|
||||
-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
|
||||
-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
|
||||
--single-class - decompile a single class
|
||||
--output-format - can be 'java' or 'json' (default: java)
|
||||
-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-debug-info - disable debug info
|
||||
--no-inline-anonymous - disable anonymous classes inline
|
||||
--no-replace-consts - don't replace constant value with matching constant field
|
||||
--escape-unicode - escape non latin characters in strings (with \u)
|
||||
--respect-bytecode-access-modifiers - don't change original access modifiers
|
||||
--deobf - activate deobfuscation
|
||||
--deobf-min - min length of name, renamed if shorter (default: 3)
|
||||
--deobf-max - max length of name, renamed if longer (default: 64)
|
||||
--deobf-rewrite-cfg - force to save deobfuscation map
|
||||
--deobf-use-sourcename - use source file name as class name alias
|
||||
--rename-flags - what to rename, comma-separated, 'case' for system case sensitivity, 'valid' for java identifiers, 'printable' characters, 'none' or 'all'
|
||||
--fs-case-sensitive - treat filesystem as case sensitive, false by default
|
||||
--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
|
||||
--version - print jadx version
|
||||
-h, --help - print this help
|
||||
Example:
|
||||
jadx -d out classes.dex
|
||||
jadx --rename-flags "none" classes.dex
|
||||
jadx --rename-flags "valid,printable" classes.dex
|
||||
```
|
||||
These options also worked on jadx-gui running from command line and override options from preferences dialog
|
||||
|
||||
|
||||
+121
-81
@@ -1,7 +1,9 @@
|
||||
import com.diffplug.spotless.LineEnding
|
||||
|
||||
plugins {
|
||||
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'
|
||||
id 'org.sonarqube' version '2.7'
|
||||
id 'com.github.ben-manes.versions' version '0.21.0'
|
||||
id "com.diffplug.gradle.spotless" version "3.21.1"
|
||||
}
|
||||
|
||||
ext.jadxVersion = System.getenv('JADX_VERSION') ?: "dev"
|
||||
@@ -9,121 +11,159 @@ version = jadxVersion
|
||||
println("jadx version: ${jadxVersion}")
|
||||
|
||||
allprojects {
|
||||
apply plugin: 'java'
|
||||
apply plugin: 'groovy'
|
||||
apply plugin: 'jacoco'
|
||||
apply plugin: 'com.github.ksoichiro.console.reporter'
|
||||
apply plugin: 'java'
|
||||
apply plugin: 'jacoco'
|
||||
apply plugin: 'checkstyle'
|
||||
|
||||
version = jadxVersion
|
||||
version = jadxVersion
|
||||
|
||||
tasks.withType(JavaCompile) {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
tasks.withType(JavaCompile) {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
|
||||
if (!"$it".contains(':jadx-samples:')) {
|
||||
options.compilerArgs << '-Xlint' << '-Xlint:unchecked' << '-Xlint:deprecation'
|
||||
}
|
||||
}
|
||||
if (!"$it".contains(':jadx-samples:')) {
|
||||
options.compilerArgs << '-Xlint' << '-Xlint:unchecked' << '-Xlint:deprecation'
|
||||
}
|
||||
}
|
||||
|
||||
compileJava {
|
||||
options.encoding = "UTF-8"
|
||||
}
|
||||
compileJava {
|
||||
options.encoding = "UTF-8"
|
||||
}
|
||||
|
||||
jar {
|
||||
version = jadxVersion
|
||||
manifest {
|
||||
mainAttributes('jadx-version': jadxVersion)
|
||||
}
|
||||
}
|
||||
jar {
|
||||
version = jadxVersion
|
||||
manifest {
|
||||
mainAttributes('jadx-version': jadxVersion)
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile 'org.slf4j:slf4j-api:1.7.25'
|
||||
dependencies {
|
||||
compile 'org.slf4j:slf4j-api:1.7.26'
|
||||
|
||||
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:2.20.1'
|
||||
testCompile 'org.spockframework:spock-core:1.1-groovy-2.4'
|
||||
testCompile 'cglib:cglib-nodep:3.2.7'
|
||||
}
|
||||
testCompile 'ch.qos.logback:logback-classic:1.2.3'
|
||||
testCompile 'org.hamcrest:hamcrest-library:2.1'
|
||||
testCompile 'org.mockito:mockito-core:2.25.1'
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
jcenter()
|
||||
}
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.4.1'
|
||||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.4.1'
|
||||
|
||||
jacoco {
|
||||
toolVersion = "0.8.1"
|
||||
}
|
||||
jacocoTestReport {
|
||||
reports {
|
||||
xml.enabled = true
|
||||
html.enabled = true
|
||||
}
|
||||
}
|
||||
testCompile 'org.eclipse.jdt.core.compiler:ecj:4.6.1'
|
||||
}
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
jcenter()
|
||||
google()
|
||||
}
|
||||
|
||||
jacoco {
|
||||
toolVersion = "0.8.2"
|
||||
}
|
||||
jacocoTestReport {
|
||||
reports {
|
||||
xml.enabled = true
|
||||
html.enabled = true
|
||||
}
|
||||
}
|
||||
|
||||
checkstyleMain {
|
||||
// exclude all sources in samples module
|
||||
exclude '**/samples/**'
|
||||
}
|
||||
}
|
||||
|
||||
sonarqube {
|
||||
properties {
|
||||
property 'sonar.exclusions', '**/jadx/samples/**/*,**/test-app/**/*'
|
||||
property 'sonar.coverage.exclusions', '**/jadx/gui/**/*'
|
||||
}
|
||||
properties {
|
||||
property 'sonar.exclusions', '**/jadx/samples/**/*,**/test-app/**/*'
|
||||
property 'sonar.coverage.exclusions', '**/jadx/gui/**/*'
|
||||
}
|
||||
}
|
||||
|
||||
spotless {
|
||||
java {
|
||||
target fileTree(rootDir).matching {
|
||||
include 'jadx-cli/src/**/java/**/*.java'
|
||||
include 'jadx-core/src/**/java/**/*.java'
|
||||
include 'jadx-gui/src/**/java/**/*.java'
|
||||
}
|
||||
|
||||
importOrderFile 'config/code-formatter/eclipse.importorder'
|
||||
eclipse().configFile 'config/code-formatter/eclipse.xml'
|
||||
removeUnusedImports()
|
||||
|
||||
lineEndings(LineEnding.UNIX)
|
||||
encoding("UTF-8")
|
||||
trimTrailingWhitespace()
|
||||
endWithNewline()
|
||||
}
|
||||
format 'misc', {
|
||||
target '**/*.gradle', '**/*.md', '**/*.xml', '**/.gitignore', '**/.properties'
|
||||
targetExclude "jadx-test-app/test-app/**", ".gradle/**", ".idea/**"
|
||||
|
||||
lineEndings(LineEnding.UNIX)
|
||||
encoding("UTF-8")
|
||||
trimTrailingWhitespace()
|
||||
endWithNewline()
|
||||
}
|
||||
}
|
||||
|
||||
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')
|
||||
}
|
||||
}
|
||||
}
|
||||
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']) {
|
||||
destinationDir file("$buildDir/jadx")
|
||||
['jadx-cli', 'jadx-gui'].each {
|
||||
from tasks.getByPath(":${it}:installDist").destinationDir
|
||||
}
|
||||
destinationDir file("$buildDir/jadx")
|
||||
['jadx-cli', 'jadx-gui'].each {
|
||||
from tasks.getByPath(":${it}:installDist").destinationDir
|
||||
}
|
||||
}
|
||||
|
||||
task pack(type: Zip, dependsOn: copyArtifacts) {
|
||||
destinationDir buildDir
|
||||
archiveName "jadx-${jadxVersion}.zip"
|
||||
from copyArtifacts.destinationDir
|
||||
destinationDir buildDir
|
||||
archiveName "jadx-${jadxVersion}.zip"
|
||||
from copyArtifacts.destinationDir
|
||||
}
|
||||
|
||||
task copyExe(type: Copy, dependsOn: 'jadx-gui:createExe') {
|
||||
group 'jadx'
|
||||
description = 'Copy exe to build dir'
|
||||
destinationDir buildDir
|
||||
from tasks.getByPath('jadx-gui:createExe').outputs
|
||||
include '*.exe'
|
||||
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'
|
||||
group 'jadx'
|
||||
description = 'Build jadx distribution zip'
|
||||
}
|
||||
|
||||
task samples(dependsOn: 'jadx-samples:samples') {
|
||||
group 'jadx'
|
||||
group 'jadx'
|
||||
}
|
||||
|
||||
task testAppCheck(dependsOn: 'jadx-test-app:testAppCheck') {
|
||||
group 'jadx'
|
||||
group 'jadx'
|
||||
}
|
||||
|
||||
task cleanBuildDir(type: Delete) {
|
||||
group 'jadx'
|
||||
delete buildDir
|
||||
group 'jadx'
|
||||
delete buildDir
|
||||
}
|
||||
|
||||
build.dependsOn(dist, samples)
|
||||
test.dependsOn(samples)
|
||||
|
||||
clean.dependsOn(cleanBuildDir)
|
||||
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
<?xml version="1.0" ?>
|
||||
|
||||
<!DOCTYPE module PUBLIC
|
||||
"-//Puppy Crawl//DTD Check Configuration 1.2//EN"
|
||||
"http://www.puppycrawl.com/dtds/configuration_1_2.dtd">
|
||||
|
||||
<module name="Checker">
|
||||
<property name="fileExtensions" value="java, properties, xml"/>
|
||||
<property name="charset" value="UTF-8"/>
|
||||
|
||||
<module name="TreeWalker">
|
||||
<property name="tabWidth" value="4"/>
|
||||
<module name="RegexpSinglelineJava">
|
||||
<property name="format" value="^\t* "/>
|
||||
<property name="message" value="Indent must use tab characters"/>
|
||||
<property name="ignoreComments" value="true"/>
|
||||
</module>
|
||||
<module name="RegexpSinglelineJava">
|
||||
<property name="format" value="^(?!\s+\* $).*?\s+$"/>
|
||||
<property name="message" value="Line has trailing spaces."/>
|
||||
</module>
|
||||
<module name="AvoidEscapedUnicodeCharacters">
|
||||
<property name="allowEscapesForControlCharacters" value="true"/>
|
||||
<property name="allowByTailComment" value="true"/>
|
||||
<property name="allowNonPrintableEscapes" value="true"/>
|
||||
</module>
|
||||
|
||||
<module name="EmptyLineSeparator">
|
||||
<property name="allowNoEmptyLineBetweenFields" value="true"/>
|
||||
<property name="allowMultipleEmptyLines" value="false"/>
|
||||
</module>
|
||||
|
||||
<!-- whitespaces -->
|
||||
<module name="SingleSpaceSeparator"/>
|
||||
<module name="GenericWhitespace"/>
|
||||
<module name="MethodParamPad"/>
|
||||
<module name="NoWhitespaceBefore"/>
|
||||
<module name="OperatorWrap"/>
|
||||
<module name="ParenPad"/>
|
||||
<module name="TypecastParenPad"/>
|
||||
<module name="WhitespaceAfter"/>
|
||||
<module name="WhitespaceAround">
|
||||
<property name="allowEmptyMethods" value="true"/>
|
||||
</module>
|
||||
<!-- <module name="EmptyForIteratorPad"/> -->
|
||||
<!-- <module name="NoWhitespaceAfter"/>-->
|
||||
|
||||
<module name="NoLineWrap"/>
|
||||
|
||||
<module name="IllegalImport"/> <!-- defaults to sun.* packages -->
|
||||
<module name="RedundantImport"/>
|
||||
<module name="UnusedImports"/>
|
||||
<!-- <module name="AvoidStarImport"/> -->
|
||||
|
||||
<module name="NeedBraces"/>
|
||||
<module name="LeftCurly"/>
|
||||
<module name="RightCurly"/>
|
||||
<module name="EmptyCatchBlock">
|
||||
<property name="exceptionVariableName" value="expected|ignore"/>
|
||||
</module>
|
||||
|
||||
<!-- naming -->
|
||||
<module name="PackageName"/>
|
||||
<module name="TypeName"/>
|
||||
<module name="InterfaceTypeParameterName"/>
|
||||
<module name="ClassTypeParameterName"/>
|
||||
<module name="StaticVariableName"/>
|
||||
<module name="ConstantName"/>
|
||||
<module name="MemberName"/>
|
||||
<module name="MethodName"/>
|
||||
<module name="MethodTypeParameterName"/>
|
||||
<module name="ParameterName"/>
|
||||
<module name="LambdaParameterName"/>
|
||||
<module name="LocalVariableName"/>
|
||||
<module name="LocalFinalVariableName"/>
|
||||
<module name="CatchParameterName"/>
|
||||
<!-- <module name="HiddenField"/> -->
|
||||
|
||||
<!-- annotations -->
|
||||
<module name="AnnotationLocation"/>
|
||||
<module name="AnnotationUseStyle"/>
|
||||
<module name="MissingOverride"/>
|
||||
<!-- <module name="MissingDeprecated"/> -->
|
||||
|
||||
<module name="ModifierOrder"/>
|
||||
<!-- <module name="RedundantModifier"/> -->
|
||||
<!-- <module name="ParameterNumber"/> -->
|
||||
|
||||
<module name="EmptyStatement"/>
|
||||
<module name="DefaultComesLast"/>
|
||||
<module name="EqualsHashCode"/>
|
||||
<module name="FallThrough"/>
|
||||
<!-- <module name="IllegalCatch"/> -->
|
||||
<module name="IllegalThrows"/>
|
||||
<module name="IllegalType"/>
|
||||
<module name="InnerAssignment"/>
|
||||
<module name="MultipleVariableDeclarations"/>
|
||||
<module name="NoClone"/>
|
||||
<module name="NoFinalizer"/>
|
||||
<module name="OneStatementPerLine"/>
|
||||
<module name="PackageDeclaration"/>
|
||||
<module name="StringLiteralEquality"/>
|
||||
|
||||
<!-- design -->
|
||||
<module name="OneTopLevelClass"/>
|
||||
<module name="MutableException"/>
|
||||
<module name="InterfaceIsType"/>
|
||||
<module name="ThrowsCount">
|
||||
<property name="max" value="2"/>
|
||||
</module>
|
||||
|
||||
<!-- misc -->
|
||||
<module name="ArrayTypeStyle"/>
|
||||
<module name="OuterTypeFilename"/>
|
||||
|
||||
<!-- sizes -->
|
||||
<module name="OuterTypeNumber"/>
|
||||
|
||||
<module name="SuppressWarningsHolder"/>
|
||||
</module>
|
||||
|
||||
<module name="NewlineAtEndOfFile"/>
|
||||
<module name="SuppressWarningsFilter"/>
|
||||
</module>
|
||||
@@ -0,0 +1,8 @@
|
||||
#Import Order
|
||||
0=java
|
||||
1=javax
|
||||
2=org
|
||||
3=com
|
||||
4=
|
||||
5=jadx
|
||||
6=\#
|
||||
@@ -0,0 +1,348 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<profiles version="16">
|
||||
<profile kind="CodeFormatterProfile" name="jadx eclipse" version="16">
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_ellipsis" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_for_statment" value="common_lines"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_logical_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_method_invocation" value="common_lines"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_imports" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_switch_statement" value="common_lines"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.format_javadoc_comments" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indentation.size" value="4"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_enum_constant_declaration" value="common_lines"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.align_with_spaces" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.disabling_tag" value="@formatter:off"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.continuation_indentation" value="2"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_enum_constants" value="48"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_imports" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_package" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_if_while_statement" value="common_lines"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.indent_root_tags" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.enabling_tag" value="@formatter:on"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.count_line_length_from_starting_position" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_multiplicative_operator" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameterized_type_references" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_logical_operator" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_annotation_declaration_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_enum_constant" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_multiplicative_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_block" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.align_tags_descriptions_grouped" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.line_length" value="100"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.use_on_off_tags" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_method_body_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_loop_body_block_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_method_declaration" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_enum_constant_declaration_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.align_variable_declarations_on_columns" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_type_declaration_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_catch_clause" value="common_lines"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_additive_operator" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_relational_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_multiplicative_operator" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_anonymous_type_declaration_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_shift_operator" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_lambda_body" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.compact_else_if" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_bitwise_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_type_parameters" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_compact_loops" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_simple_for_body_on_same_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_relational_operator" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_unary_operator" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_annotation" value="separate_lines_if_wrapped"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_ellipsis" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_additive_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_string_concatenation" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.format_line_comments" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.align_type_members_on_columns" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_assignment" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_module_statements" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.align_tags_names_descriptions" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_if_then_body_block_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_conditional_expression" value="48"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.align_assignment_statements_on_columns" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block_in_case" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_conditional_expression_chain" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.format_header" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_additive_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_method_declaration" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.join_wrapped_lines" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_conditional_operator" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_shift_operator" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.align_fields_grouping_blank_lines" value="2147483647"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_bitwise_operator" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_resources_in_try" value="80"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_try_clause" value="common_lines"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_code_block_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.tabulation.size" value="4"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_bitwise_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.format_source_code" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_field" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer" value="2"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_method" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_assignment_operator" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_switch" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_type_annotation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.format_html" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_method_delcaration" value="common_lines"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_compact_if" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_lambda_body_block_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_empty_lines" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_type_arguments" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_unary_operator" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_label" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_member_type" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_logical_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_bitwise_operator" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_relational_operator" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.format_block_comments" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_lambda_arrow" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.indent_tag_description" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_string_concatenation" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_body" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_multiple_fields" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_simple_while_body_on_same_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_array_initializer" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_logical_operator" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_shift_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_lambda_declaration" value="common_lines"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_shift_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_simple_do_while_body_on_same_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_enum_declaration_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_constant" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_type_declaration" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_multiplicative_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_package" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_for_loop_header" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_additive_operator" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_simple_getter_setter_on_one_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_string_concatenation" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_lambda_arrow" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.join_lines_in_comments" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.indent_parameter_description" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.tabulation.char" value="tab"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_relational_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_string_concatenation" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_import_groups" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.lineSplit" value="140"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch" value="insert"/>
|
||||
</profile>
|
||||
</profiles>
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.2.1-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
@@ -19,5 +19,10 @@ applicationDistribution.with {
|
||||
}
|
||||
|
||||
startScripts {
|
||||
defaultJvmOpts = ['-Xms128M', '-Xmx4g']
|
||||
defaultJvmOpts = ['-Xms128M', '-Xmx4g', '-XX:+UseG1GC']
|
||||
doLast {
|
||||
def str = windowsScript.text
|
||||
str = str.replaceAll('set JAVA_EXE=%JAVA_HOME%/bin/java.exe', 'set JAVA_EXE="%JAVA_HOME%/bin/java.exe"')
|
||||
windowsScript.text = str
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
package jadx.cli;
|
||||
|
||||
import java.io.PrintStream;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.beust.jcommander.JCommander;
|
||||
import com.beust.jcommander.ParameterDescription;
|
||||
import com.beust.jcommander.ParameterException;
|
||||
import com.beust.jcommander.Parameterized;
|
||||
|
||||
import jadx.api.JadxDecompiler;
|
||||
|
||||
public class JCommanderWrapper<T> {
|
||||
private final JCommander jc;
|
||||
|
||||
public JCommanderWrapper(T obj) {
|
||||
this.jc = JCommander.newBuilder().addObject(obj).build();
|
||||
}
|
||||
|
||||
public boolean parse(String[] args) {
|
||||
try {
|
||||
jc.parse(args);
|
||||
return true;
|
||||
} catch (ParameterException e) {
|
||||
System.err.println("Arguments parse error: " + e.getMessage());
|
||||
printUsage();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void overrideProvided(T obj) {
|
||||
List<ParameterDescription> fieldsParams = jc.getParameters();
|
||||
List<ParameterDescription> parameters = new ArrayList<>(1 + fieldsParams.size());
|
||||
parameters.add(jc.getMainParameterValue());
|
||||
parameters.addAll(fieldsParams);
|
||||
for (ParameterDescription parameter : parameters) {
|
||||
if (parameter.isAssigned()) {
|
||||
// copy assigned field value to obj
|
||||
Parameterized parameterized = parameter.getParameterized();
|
||||
Object val = parameterized.get(parameter.getObject());
|
||||
parameterized.set(obj, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void printUsage() {
|
||||
// print usage in not sorted fields order (by default its sorted by description)
|
||||
PrintStream out = System.out;
|
||||
out.println();
|
||||
out.println("jadx - dex to java decompiler, version: " + JadxDecompiler.getVersion());
|
||||
out.println();
|
||||
out.println("usage: jadx [options] " + jc.getMainParameterDescription());
|
||||
out.println("options:");
|
||||
|
||||
List<ParameterDescription> params = jc.getParameters();
|
||||
Map<String, ParameterDescription> paramsMap = new LinkedHashMap<>(params.size());
|
||||
int maxNamesLen = 0;
|
||||
for (ParameterDescription p : params) {
|
||||
paramsMap.put(p.getParameterized().getName(), p);
|
||||
int len = p.getNames().length();
|
||||
if (len > maxNamesLen) {
|
||||
maxNamesLen = len;
|
||||
}
|
||||
}
|
||||
|
||||
JadxCLIArgs args = (JadxCLIArgs) jc.getObjects().get(0);
|
||||
for (Field f : getFields(args.getClass())) {
|
||||
String name = f.getName();
|
||||
ParameterDescription p = paramsMap.get(name);
|
||||
if (p == null) {
|
||||
continue;
|
||||
}
|
||||
StringBuilder opt = new StringBuilder();
|
||||
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");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all declared fields of the specified class and all super classes
|
||||
*
|
||||
* @param clazz
|
||||
* @return
|
||||
*/
|
||||
private List<Field> getFields(Class<?> clazz) {
|
||||
List<Field> fieldList = new LinkedList<>();
|
||||
while (clazz != null) {
|
||||
fieldList.addAll(Arrays.asList(clazz.getDeclaredFields()));
|
||||
clazz = clazz.getSuperclass();
|
||||
}
|
||||
return fieldList;
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
if (fieldType == String.class) {
|
||||
try {
|
||||
String val = (String) f.get(args);
|
||||
if (val != null) {
|
||||
opt.append(" (default: ").append(val).append(')');
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void addSpaces(StringBuilder str, int count) {
|
||||
for (int i = 0; i < count; i++) {
|
||||
str.append(' ');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package jadx.cli;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.api.JadxDecompiler;
|
||||
import jadx.core.utils.exceptions.JadxArgsValidateException;
|
||||
|
||||
@@ -25,7 +26,8 @@ public class JadxCLI {
|
||||
}
|
||||
|
||||
static int processAndSave(JadxCLIArgs inputArgs) {
|
||||
JadxDecompiler jadx = new JadxDecompiler(inputArgs.toJadxArgs());
|
||||
JadxArgs args = inputArgs.toJadxArgs();
|
||||
JadxDecompiler jadx = new JadxDecompiler(args);
|
||||
try {
|
||||
jadx.load();
|
||||
} catch (JadxArgsValidateException e) {
|
||||
|
||||
@@ -1,120 +1,153 @@
|
||||
package jadx.cli;
|
||||
|
||||
import java.io.PrintStream;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Arrays;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
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;
|
||||
|
||||
import com.beust.jcommander.IStringConverter;
|
||||
import com.beust.jcommander.Parameter;
|
||||
|
||||
import ch.qos.logback.classic.spi.ILoggingEvent;
|
||||
import ch.qos.logback.core.Appender;
|
||||
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.api.JadxArgs.RenameEnum;
|
||||
import jadx.api.JadxDecompiler;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
import jadx.core.utils.files.FileUtils;
|
||||
|
||||
public class JadxCLIArgs {
|
||||
|
||||
@Parameter(description = "<input file> (.apk, .dex, .jar or .class)")
|
||||
@Parameter(description = "<input file> (.apk, .dex, .jar, .class, .smali, .zip, .aar, .arsc)")
|
||||
protected List<String> files = new ArrayList<>(1);
|
||||
|
||||
@Parameter(names = {"-d", "--output-dir"}, description = "output directory")
|
||||
@Parameter(names = { "-d", "--output-dir" }, description = "output directory")
|
||||
protected String outDir;
|
||||
|
||||
@Parameter(names = {"-ds", "--output-dir-src"}, description = "output directory for sources")
|
||||
@Parameter(names = { "-ds", "--output-dir-src" }, description = "output directory for sources")
|
||||
protected String outDirSrc;
|
||||
|
||||
@Parameter(names = {"-dr", "--output-dir-res"}, description = "output directory for resources")
|
||||
@Parameter(names = { "-dr", "--output-dir-res" }, description = "output directory for resources")
|
||||
protected String outDirRes;
|
||||
|
||||
@Parameter(names = {"-r", "--no-res"}, description = "do not decode resources")
|
||||
@Parameter(names = { "-r", "--no-res" }, description = "do not decode resources")
|
||||
protected boolean skipResources = false;
|
||||
|
||||
@Parameter(names = {"-s", "--no-src"}, description = "do not decompile source code")
|
||||
@Parameter(names = { "-s", "--no-src" }, description = "do not decompile source code")
|
||||
protected boolean skipSources = false;
|
||||
|
||||
@Parameter(names = {"-e", "--export-gradle"}, description = "save as android gradle project")
|
||||
@Parameter(names = { "--single-class" }, description = "decompile a single class")
|
||||
protected String singleClass = null;
|
||||
|
||||
@Parameter(names = { "--output-format" }, description = "can be 'java' or 'json'")
|
||||
protected String outputFormat = "java";
|
||||
|
||||
@Parameter(names = { "-e", "--export-gradle" }, description = "save as android gradle project")
|
||||
protected boolean exportAsGradleProject = false;
|
||||
|
||||
@Parameter(names = {"-j", "--threads-count"}, description = "processing threads count")
|
||||
@Parameter(names = { "-j", "--threads-count" }, description = "processing threads count")
|
||||
protected int threadsCount = JadxArgs.DEFAULT_THREADS_COUNT;
|
||||
|
||||
@Parameter(names = {"--show-bad-code"}, description = "show inconsistent code (incorrectly decompiled)")
|
||||
@Parameter(names = { "--show-bad-code" }, description = "show inconsistent code (incorrectly decompiled)")
|
||||
protected boolean showInconsistentCode = false;
|
||||
|
||||
@Parameter(names = {"--no-imports"}, description = "disable use of imports, always write entire package name")
|
||||
@Parameter(names = { "--no-imports" }, description = "disable use of imports, always write entire package name")
|
||||
protected boolean useImports = true;
|
||||
|
||||
@Parameter(names = { "--no-debug-info" }, description = "disable debug info")
|
||||
protected boolean debugInfo = true;
|
||||
|
||||
@Parameter(names = { "--no-inline-anonymous" }, description = "disable anonymous classes inline")
|
||||
protected boolean inlineAnonymousClasses = 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)")
|
||||
@Parameter(names = { "--escape-unicode" }, description = "escape non latin characters in strings (with \\u)")
|
||||
protected boolean escapeUnicode = false;
|
||||
|
||||
@Parameter(names = {"--deobf"}, description = "activate deobfuscation")
|
||||
@Parameter(names = { "--respect-bytecode-access-modifiers" }, description = "don't change original access modifiers")
|
||||
protected boolean respectBytecodeAccessModifiers = false;
|
||||
|
||||
@Parameter(names = { "--deobf" }, description = "activate deobfuscation")
|
||||
protected boolean deobfuscationOn = false;
|
||||
|
||||
@Parameter(names = {"--deobf-min"}, description = "min length of name, renamed if shorter")
|
||||
@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")
|
||||
@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")
|
||||
@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 = { "--deobf-use-sourcename" }, description = "use source file name as class name alias")
|
||||
protected boolean deobfuscationUseSourceNameAsAlias = false;
|
||||
|
||||
@Parameter(names = {"--cfg"}, description = "save methods control flow graph to dot file")
|
||||
@Parameter(
|
||||
names = { "--rename-flags" },
|
||||
description = "what to rename, comma-separated,"
|
||||
+ " 'case' for system case sensitivity,"
|
||||
+ " 'valid' for java identifiers,"
|
||||
+ " 'printable' characters,"
|
||||
+ " 'none' or 'all' (default)",
|
||||
converter = RenameConverter.class
|
||||
)
|
||||
protected Set<RenameEnum> renameFlags = EnumSet.allOf(RenameEnum.class);
|
||||
|
||||
@Parameter(names = { "--fs-case-sensitive" }, description = "treat filesystem as case sensitive, false by default")
|
||||
protected boolean fsCaseSensitive = false;
|
||||
|
||||
@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)")
|
||||
@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)")
|
||||
@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")
|
||||
@Parameter(names = { "-v", "--verbose" }, description = "verbose output")
|
||||
protected boolean verbose = false;
|
||||
|
||||
@Parameter(names = {"--version"}, description = "print jadx version")
|
||||
@Parameter(names = { "--version" }, description = "print jadx version")
|
||||
protected boolean printVersion = false;
|
||||
|
||||
@Parameter(names = {"-h", "--help"}, description = "print this help", help = true)
|
||||
@Parameter(names = { "-h", "--help" }, description = "print this help", help = true)
|
||||
protected boolean printHelp = false;
|
||||
|
||||
public boolean processArgs(String[] args) {
|
||||
return parse(args) && process();
|
||||
JCommanderWrapper<JadxCLIArgs> jcw = new JCommanderWrapper<>(this);
|
||||
return jcw.parse(args) && process(jcw);
|
||||
}
|
||||
|
||||
private boolean parse(String[] args) {
|
||||
try {
|
||||
makeJCommander().parse(args);
|
||||
return true;
|
||||
} catch (ParameterException e) {
|
||||
System.err.println("Arguments parse error: " + e.getMessage());
|
||||
printUsage();
|
||||
/**
|
||||
* Set values only for options provided in cmd.
|
||||
* Used to merge saved options and options passed in command line.
|
||||
*/
|
||||
public boolean overrideProvided(String[] args) {
|
||||
JCommanderWrapper<JadxCLIArgs> jcw = new JCommanderWrapper<>(newInstance());
|
||||
if (!jcw.parse(args)) {
|
||||
return false;
|
||||
}
|
||||
jcw.overrideProvided(this);
|
||||
return process(jcw);
|
||||
}
|
||||
|
||||
private JCommander makeJCommander() {
|
||||
return JCommander.newBuilder().addObject(this).build();
|
||||
protected JadxCLIArgs newInstance() {
|
||||
return new JadxCLIArgs();
|
||||
}
|
||||
|
||||
private boolean process() {
|
||||
private boolean process(JCommanderWrapper<JadxCLIArgs> jcw) {
|
||||
if (printHelp) {
|
||||
printUsage();
|
||||
jcw.printUsage();
|
||||
return false;
|
||||
}
|
||||
if (printVersion) {
|
||||
@@ -136,77 +169,24 @@ public class JadxCLIArgs {
|
||||
}
|
||||
} catch (JadxException e) {
|
||||
System.err.println("ERROR: " + e.getMessage());
|
||||
printUsage();
|
||||
jcw.printUsage();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void printUsage() {
|
||||
JCommander jc = makeJCommander();
|
||||
// print usage in not sorted fields order (by default its sorted by description)
|
||||
PrintStream out = System.out;
|
||||
out.println();
|
||||
out.println("jadx - dex to java decompiler, version: " + JadxDecompiler.getVersion());
|
||||
out.println();
|
||||
out.println("usage: jadx [options] " + jc.getMainParameterDescription());
|
||||
out.println("options:");
|
||||
|
||||
List<ParameterDescription> params = jc.getParameters();
|
||||
Map<String, ParameterDescription> paramsMap = new LinkedHashMap<>(params.size());
|
||||
int maxNamesLen = 0;
|
||||
for (ParameterDescription p : params) {
|
||||
paramsMap.put(p.getParameterized().getName(), p);
|
||||
int len = p.getNames().length();
|
||||
if (len > maxNamesLen) {
|
||||
maxNamesLen = len;
|
||||
}
|
||||
}
|
||||
JadxCLIArgs args = new JadxCLIArgs();
|
||||
Field[] fields = args.getClass().getDeclaredFields();
|
||||
for (Field f : fields) {
|
||||
String name = f.getName();
|
||||
ParameterDescription p = paramsMap.get(name);
|
||||
if (p == null) {
|
||||
continue;
|
||||
}
|
||||
StringBuilder opt = new StringBuilder();
|
||||
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");
|
||||
}
|
||||
|
||||
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) {
|
||||
for (int i = 0; i < count; i++) {
|
||||
str.append(' ');
|
||||
}
|
||||
}
|
||||
|
||||
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.setOutputFormat(JadxArgs.OutputFormatEnum.valueOf(outputFormat.toUpperCase()));
|
||||
args.setThreadsCount(threadsCount);
|
||||
args.setSkipSources(skipSources);
|
||||
if (singleClass != null) {
|
||||
args.setClassFilter(className -> singleClass.equals(className));
|
||||
}
|
||||
args.setSkipResources(skipResources);
|
||||
args.setFallbackMode(fallbackMode);
|
||||
args.setShowInconsistentCode(showInconsistentCode);
|
||||
@@ -219,8 +199,15 @@ public class JadxCLIArgs {
|
||||
args.setDeobfuscationMaxLength(deobfuscationMaxLength);
|
||||
args.setUseSourceNameAsClassAlias(deobfuscationUseSourceNameAsAlias);
|
||||
args.setEscapeUnicode(escapeUnicode);
|
||||
args.setRespectBytecodeAccModifiers(respectBytecodeAccessModifiers);
|
||||
args.setExportAsGradleProject(exportAsGradleProject);
|
||||
args.setUseImports(useImports);
|
||||
args.setDebugInfo(debugInfo);
|
||||
args.setInlineAnonymousClasses(inlineAnonymousClasses);
|
||||
args.setRenameCaseSensitive(isRenameCaseSensitive());
|
||||
args.setRenameValid(isRenameValid());
|
||||
args.setRenamePrintable(isRenamePrintable());
|
||||
args.setFsCaseSensitive(fsCaseSensitive);
|
||||
return args;
|
||||
}
|
||||
|
||||
@@ -264,6 +251,14 @@ public class JadxCLIArgs {
|
||||
return useImports;
|
||||
}
|
||||
|
||||
public boolean isDebugInfo() {
|
||||
return debugInfo;
|
||||
}
|
||||
|
||||
public boolean isInlineAnonymousClasses() {
|
||||
return inlineAnonymousClasses;
|
||||
}
|
||||
|
||||
public boolean isDeobfuscationOn() {
|
||||
return deobfuscationOn;
|
||||
}
|
||||
@@ -284,10 +279,6 @@ public class JadxCLIArgs {
|
||||
return deobfuscationUseSourceNameAsAlias;
|
||||
}
|
||||
|
||||
public boolean escapeUnicode() {
|
||||
return escapeUnicode;
|
||||
}
|
||||
|
||||
public boolean isEscapeUnicode() {
|
||||
return escapeUnicode;
|
||||
}
|
||||
@@ -304,7 +295,59 @@ public class JadxCLIArgs {
|
||||
return replaceConsts;
|
||||
}
|
||||
|
||||
public boolean isRespectBytecodeAccessModifiers() {
|
||||
return respectBytecodeAccessModifiers;
|
||||
}
|
||||
|
||||
public boolean isExportAsGradleProject() {
|
||||
return exportAsGradleProject;
|
||||
}
|
||||
|
||||
public boolean isRenameCaseSensitive() {
|
||||
return renameFlags.contains(RenameEnum.CASE);
|
||||
}
|
||||
|
||||
public boolean isRenameValid() {
|
||||
return renameFlags.contains(RenameEnum.VALID);
|
||||
}
|
||||
|
||||
public boolean isRenamePrintable() {
|
||||
return renameFlags.contains(RenameEnum.PRINTABLE);
|
||||
}
|
||||
|
||||
public boolean isFsCaseSensitive() {
|
||||
return fsCaseSensitive;
|
||||
}
|
||||
|
||||
static class RenameConverter implements IStringConverter<Set<RenameEnum>> {
|
||||
private final String paramName;
|
||||
|
||||
RenameConverter(String paramName) {
|
||||
this.paramName = paramName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<RenameEnum> convert(String value) {
|
||||
if (value.equalsIgnoreCase("NONE")) {
|
||||
return EnumSet.noneOf(RenameEnum.class);
|
||||
}
|
||||
if (value.equalsIgnoreCase("ALL")) {
|
||||
return EnumSet.allOf(RenameEnum.class);
|
||||
}
|
||||
Set<RenameEnum> set = EnumSet.noneOf(RenameEnum.class);
|
||||
for (String s : value.split(",")) {
|
||||
try {
|
||||
set.add(RenameEnum.valueOf(s.toUpperCase(Locale.ROOT)));
|
||||
} catch (IllegalArgumentException e) {
|
||||
String values = Arrays.stream(RenameEnum.values())
|
||||
.map(v -> '\'' + v.name().toLowerCase(Locale.ROOT) + '\'')
|
||||
.collect(Collectors.joining(", "));
|
||||
throw new IllegalArgumentException(
|
||||
'\'' + s + "' is unknown for parameter " + paramName
|
||||
+ ", possible values are " + values);
|
||||
}
|
||||
}
|
||||
return set;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
<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>
|
||||
|
||||
<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="DEBUG">
|
||||
<appender-ref ref="STDOUT"/>
|
||||
</root>
|
||||
|
||||
<root level="DEBUG">
|
||||
<appender-ref ref="STDOUT"/>
|
||||
</root>
|
||||
</configuration>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package jadx.cli;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
public class JadxCLIArgsTest {
|
||||
|
||||
@@ -30,11 +30,38 @@ public class JadxCLIArgsTest {
|
||||
assertThat(parse("").isSkipSources(), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOptionsOverride() {
|
||||
assertThat(override(new JadxCLIArgs(), "--no-imports").isUseImports(), is(false));
|
||||
assertThat(override(new JadxCLIArgs(), "--no-debug-info").isDebugInfo(), is(false));
|
||||
assertThat(override(new JadxCLIArgs(), "").isUseImports(), is(true));
|
||||
|
||||
JadxCLIArgs args = new JadxCLIArgs();
|
||||
args.useImports = false;
|
||||
assertThat(override(args, "--no-imports").isUseImports(), is(false));
|
||||
args.debugInfo = false;
|
||||
assertThat(override(args, "--no-debug-info").isDebugInfo(), is(false));
|
||||
|
||||
args = new JadxCLIArgs();
|
||||
args.useImports = false;
|
||||
assertThat(override(args, "").isUseImports(), is(false));
|
||||
}
|
||||
|
||||
private JadxCLIArgs parse(String... args) {
|
||||
JadxCLIArgs jadxArgs = new JadxCLIArgs();
|
||||
return parse(new JadxCLIArgs(), args);
|
||||
}
|
||||
|
||||
private JadxCLIArgs parse(JadxCLIArgs jadxArgs, String... args) {
|
||||
boolean res = jadxArgs.processArgs(args);
|
||||
assertThat(res, is(true));
|
||||
LOG.info("Jadx args: {}", jadxArgs.toJadxArgs());
|
||||
return jadxArgs;
|
||||
}
|
||||
|
||||
private JadxCLIArgs override(JadxCLIArgs jadxArgs, String... args) {
|
||||
boolean res = jadxArgs.overrideProvided(args);
|
||||
assertThat(res, is(true));
|
||||
LOG.info("Jadx args: {}", jadxArgs.toJadxArgs());
|
||||
return jadxArgs;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
package jadx.cli;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jadx.api.JadxArgs.RenameEnum;
|
||||
import jadx.cli.JadxCLIArgs.RenameConverter;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public class RenameConverterTest {
|
||||
|
||||
private RenameConverter converter;
|
||||
|
||||
@BeforeEach
|
||||
public void init() {
|
||||
converter = new RenameConverter("someParam");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void all() {
|
||||
Set<RenameEnum> set = converter.convert("all");
|
||||
assertEquals(3, set.size());
|
||||
assertTrue(set.contains(RenameEnum.CASE));
|
||||
assertTrue(set.contains(RenameEnum.VALID));
|
||||
assertTrue(set.contains(RenameEnum.PRINTABLE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void none() {
|
||||
Set<RenameEnum> set = converter.convert("none");
|
||||
assertTrue(set.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void wrong() {
|
||||
IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class,
|
||||
() -> converter.convert("wrong"),
|
||||
"Expected convert() to throw, but it didn't");
|
||||
|
||||
assertEquals("'wrong' is unknown for parameter someParam, "
|
||||
+ "possible values are 'case', 'valid', 'printable'",
|
||||
thrown.getMessage());
|
||||
}
|
||||
}
|
||||
+12
-9
@@ -3,15 +3,18 @@ ext.jadxClasspath = 'clsp-data/android-5.1.jar'
|
||||
dependencies {
|
||||
runtime files(jadxClasspath)
|
||||
|
||||
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'
|
||||
compile files('lib/dx-1.16.jar')
|
||||
|
||||
testCompile 'org.smali:smali:2.2.4'
|
||||
testCompile 'org.smali:baksmali:2.2.4'
|
||||
compile 'org.ow2.asm:asm:7.1'
|
||||
compile 'org.jetbrains:annotations:17.0.0'
|
||||
compile 'uk.com.robust-it:cloning:1.9.12'
|
||||
compile 'com.google.code.gson:gson:2.8.5'
|
||||
|
||||
testCompile 'org.apache.commons:commons-lang3:3.7'
|
||||
compile 'org.smali:baksmali:2.2.7'
|
||||
compile('org.smali:smali:2.2.7') {
|
||||
exclude group: 'com.google.guava'
|
||||
}
|
||||
compile 'com.google.guava:guava:27.1-jre'
|
||||
|
||||
testCompile 'org.apache.commons:commons-lang3:3.8.1'
|
||||
}
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -57,6 +57,14 @@ public final class CodePosition {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return line + ":" + offset + (node != null ? " " + node : "");
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(line);
|
||||
if (offset != 0) {
|
||||
sb.append(':').append(offset);
|
||||
}
|
||||
if (node != null) {
|
||||
sb.append(' ').append(node);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,11 @@ package jadx.api;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public class JadxArgs {
|
||||
|
||||
@@ -27,12 +31,19 @@ public class JadxArgs {
|
||||
private boolean showInconsistentCode = false;
|
||||
|
||||
private boolean useImports = true;
|
||||
private boolean debugInfo = true;
|
||||
private boolean inlineAnonymousClasses = true;
|
||||
|
||||
private boolean isSkipResources = false;
|
||||
private boolean isSkipSources = false;
|
||||
private boolean skipResources = false;
|
||||
private boolean skipSources = false;
|
||||
|
||||
private boolean isDeobfuscationOn = false;
|
||||
private boolean isDeobfuscationForceSave = false;
|
||||
/**
|
||||
* Predicate that allows to filter the classes to be process based on their full name
|
||||
*/
|
||||
private Predicate<String> classFilter = null;
|
||||
|
||||
private boolean deobfuscationOn = false;
|
||||
private boolean deobfuscationForceSave = false;
|
||||
private boolean useSourceNameAsClassAlias = false;
|
||||
|
||||
private int deobfuscationMinLength = 0;
|
||||
@@ -40,8 +51,23 @@ public class JadxArgs {
|
||||
|
||||
private boolean escapeUnicode = false;
|
||||
private boolean replaceConsts = true;
|
||||
private boolean respectBytecodeAccModifiers = false;
|
||||
private boolean exportAsGradleProject = false;
|
||||
|
||||
private boolean fsCaseSensitive;
|
||||
|
||||
public enum RenameEnum {
|
||||
CASE, VALID, PRINTABLE
|
||||
}
|
||||
|
||||
private Set<RenameEnum> renameFlags = EnumSet.allOf(RenameEnum.class);
|
||||
|
||||
public enum OutputFormatEnum {
|
||||
JAVA, JSON
|
||||
}
|
||||
|
||||
private OutputFormatEnum outputFormat = OutputFormatEnum.JAVA;
|
||||
|
||||
public JadxArgs() {
|
||||
// use default options
|
||||
}
|
||||
@@ -56,6 +82,10 @@ public class JadxArgs {
|
||||
return inputFiles;
|
||||
}
|
||||
|
||||
public void setInputFile(File inputFile) {
|
||||
this.inputFiles = Collections.singletonList(inputFile);
|
||||
}
|
||||
|
||||
public void setInputFiles(List<File> inputFiles) {
|
||||
this.inputFiles = inputFiles;
|
||||
}
|
||||
@@ -132,36 +162,60 @@ public class JadxArgs {
|
||||
this.useImports = useImports;
|
||||
}
|
||||
|
||||
public boolean isDebugInfo() {
|
||||
return debugInfo;
|
||||
}
|
||||
|
||||
public void setDebugInfo(boolean debugInfo) {
|
||||
this.debugInfo = debugInfo;
|
||||
}
|
||||
|
||||
public boolean isInlineAnonymousClasses() {
|
||||
return inlineAnonymousClasses;
|
||||
}
|
||||
|
||||
public void setInlineAnonymousClasses(boolean inlineAnonymousClasses) {
|
||||
this.inlineAnonymousClasses = inlineAnonymousClasses;
|
||||
}
|
||||
|
||||
public boolean isSkipResources() {
|
||||
return isSkipResources;
|
||||
return skipResources;
|
||||
}
|
||||
|
||||
public void setSkipResources(boolean skipResources) {
|
||||
isSkipResources = skipResources;
|
||||
this.skipResources = skipResources;
|
||||
}
|
||||
|
||||
public boolean isSkipSources() {
|
||||
return isSkipSources;
|
||||
return skipSources;
|
||||
}
|
||||
|
||||
public void setSkipSources(boolean skipSources) {
|
||||
isSkipSources = skipSources;
|
||||
this.skipSources = skipSources;
|
||||
}
|
||||
|
||||
public Predicate<String> getClassFilter() {
|
||||
return classFilter;
|
||||
}
|
||||
|
||||
public void setClassFilter(Predicate<String> classFilter) {
|
||||
this.classFilter = classFilter;
|
||||
}
|
||||
|
||||
public boolean isDeobfuscationOn() {
|
||||
return isDeobfuscationOn;
|
||||
return deobfuscationOn;
|
||||
}
|
||||
|
||||
public void setDeobfuscationOn(boolean deobfuscationOn) {
|
||||
isDeobfuscationOn = deobfuscationOn;
|
||||
this.deobfuscationOn = deobfuscationOn;
|
||||
}
|
||||
|
||||
public boolean isDeobfuscationForceSave() {
|
||||
return isDeobfuscationForceSave;
|
||||
return deobfuscationForceSave;
|
||||
}
|
||||
|
||||
public void setDeobfuscationForceSave(boolean deobfuscationForceSave) {
|
||||
isDeobfuscationForceSave = deobfuscationForceSave;
|
||||
this.deobfuscationForceSave = deobfuscationForceSave;
|
||||
}
|
||||
|
||||
public boolean isUseSourceNameAsClassAlias() {
|
||||
@@ -204,6 +258,14 @@ public class JadxArgs {
|
||||
this.replaceConsts = replaceConsts;
|
||||
}
|
||||
|
||||
public boolean isRespectBytecodeAccModifiers() {
|
||||
return respectBytecodeAccModifiers;
|
||||
}
|
||||
|
||||
public void setRespectBytecodeAccModifiers(boolean respectBytecodeAccModifiers) {
|
||||
this.respectBytecodeAccModifiers = respectBytecodeAccModifiers;
|
||||
}
|
||||
|
||||
public boolean isExportAsGradleProject() {
|
||||
return exportAsGradleProject;
|
||||
}
|
||||
@@ -212,30 +274,84 @@ public class JadxArgs {
|
||||
this.exportAsGradleProject = exportAsGradleProject;
|
||||
}
|
||||
|
||||
public boolean isFsCaseSensitive() {
|
||||
return fsCaseSensitive;
|
||||
}
|
||||
|
||||
public void setFsCaseSensitive(boolean fsCaseSensitive) {
|
||||
this.fsCaseSensitive = fsCaseSensitive;
|
||||
}
|
||||
|
||||
public boolean isRenameCaseSensitive() {
|
||||
return renameFlags.contains(RenameEnum.CASE);
|
||||
}
|
||||
|
||||
public void setRenameCaseSensitive(boolean renameCaseSensitive) {
|
||||
updateRenameFlag(renameCaseSensitive, RenameEnum.CASE);
|
||||
}
|
||||
|
||||
public boolean isRenameValid() {
|
||||
return renameFlags.contains(RenameEnum.VALID);
|
||||
}
|
||||
|
||||
public void setRenameValid(boolean renameValid) {
|
||||
updateRenameFlag(renameValid, RenameEnum.VALID);
|
||||
}
|
||||
|
||||
public boolean isRenamePrintable() {
|
||||
return renameFlags.contains(RenameEnum.PRINTABLE);
|
||||
}
|
||||
|
||||
public void setRenamePrintable(boolean renamePrintable) {
|
||||
updateRenameFlag(renamePrintable, RenameEnum.PRINTABLE);
|
||||
}
|
||||
|
||||
private void updateRenameFlag(boolean enabled, RenameEnum flag) {
|
||||
if (enabled) {
|
||||
renameFlags.add(flag);
|
||||
} else {
|
||||
renameFlags.remove(flag);
|
||||
}
|
||||
}
|
||||
|
||||
public OutputFormatEnum getOutputFormat() {
|
||||
return outputFormat;
|
||||
}
|
||||
|
||||
public boolean isJsonOutput() {
|
||||
return outputFormat == OutputFormatEnum.JSON;
|
||||
}
|
||||
|
||||
public void setOutputFormat(OutputFormatEnum outputFormat) {
|
||||
this.outputFormat = outputFormat;
|
||||
}
|
||||
|
||||
@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();
|
||||
return "JadxArgs{" + "inputFiles=" + inputFiles
|
||||
+ ", outDir=" + outDir
|
||||
+ ", outDirSrc=" + outDirSrc
|
||||
+ ", outDirRes=" + outDirRes
|
||||
+ ", threadsCount=" + threadsCount
|
||||
+ ", cfgOutput=" + cfgOutput
|
||||
+ ", rawCFGOutput=" + rawCFGOutput
|
||||
+ ", fallbackMode=" + fallbackMode
|
||||
+ ", showInconsistentCode=" + showInconsistentCode
|
||||
+ ", useImports=" + useImports
|
||||
+ ", skipResources=" + skipResources
|
||||
+ ", skipSources=" + skipSources
|
||||
+ ", deobfuscationOn=" + deobfuscationOn
|
||||
+ ", deobfuscationForceSave=" + deobfuscationForceSave
|
||||
+ ", useSourceNameAsClassAlias=" + useSourceNameAsClassAlias
|
||||
+ ", deobfuscationMinLength=" + deobfuscationMinLength
|
||||
+ ", deobfuscationMaxLength=" + deobfuscationMaxLength
|
||||
+ ", escapeUnicode=" + escapeUnicode
|
||||
+ ", replaceConsts=" + replaceConsts
|
||||
+ ", respectBytecodeAccModifiers=" + respectBytecodeAccModifiers
|
||||
+ ", exportAsGradleProject=" + exportAsGradleProject
|
||||
+ ", fsCaseSensitive=" + fsCaseSensitive
|
||||
+ ", renameFlags=" + renameFlags
|
||||
+ ", outputFormat=" + outputFormat
|
||||
+ '}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,13 +53,18 @@ public class JadxArgsValidator {
|
||||
} else {
|
||||
outDir = makeDirFromInput(args);
|
||||
}
|
||||
args.setOutDir(outDir);
|
||||
}
|
||||
if (srcDir == null) {
|
||||
args.setOutDirSrc(new File(args.getOutDir(), JadxArgs.DEFAULT_SRC_DIR));
|
||||
}
|
||||
if (resDir == null) {
|
||||
args.setOutDirRes(new File(args.getOutDir(), JadxArgs.DEFAULT_RES_DIR));
|
||||
}
|
||||
args.setOutDir(outDir);
|
||||
setFromOut(args);
|
||||
|
||||
checkDir(args.getOutDir());
|
||||
checkDir(args.getOutDirSrc());
|
||||
checkDir(args.getOutDirRes());
|
||||
checkDir(args.getOutDir(), "Output");
|
||||
checkDir(args.getOutDirSrc(), "Source output");
|
||||
checkDir(args.getOutDirRes(), "Resources output");
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -72,22 +77,13 @@ public class JadxArgsValidator {
|
||||
if (pos != -1) {
|
||||
outDirName = name.substring(0, pos);
|
||||
} else {
|
||||
outDirName = name + "-" + JadxArgs.DEFAULT_OUT_DIR;
|
||||
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());
|
||||
@@ -97,9 +93,9 @@ public class JadxArgsValidator {
|
||||
}
|
||||
}
|
||||
|
||||
private static void checkDir(File dir) {
|
||||
private static void checkDir(File dir, String desc) {
|
||||
if (dir != null && dir.exists() && !dir.isDirectory()) {
|
||||
throw new JadxArgsValidateException("Output directory exists as file " + dir);
|
||||
throw new JadxArgsValidateException(desc + " directory exists as file " + dir);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package jadx.api;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.StringWriter;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
@@ -11,13 +13,20 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.jf.baksmali.Adaptors.ClassDefinition;
|
||||
import org.jf.baksmali.BaksmaliOptions;
|
||||
import org.jf.dexlib2.DexFileFactory;
|
||||
import org.jf.dexlib2.Opcodes;
|
||||
import org.jf.dexlib2.dexbacked.DexBackedClassDef;
|
||||
import org.jf.dexlib2.dexbacked.DexBackedDexFile;
|
||||
import org.jf.util.IndentingWriter;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.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;
|
||||
@@ -26,6 +35,7 @@ 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.Utils;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
import jadx.core.utils.files.InputFile;
|
||||
import jadx.core.xmlgen.BinaryXMLParser;
|
||||
@@ -33,7 +43,9 @@ import jadx.core.xmlgen.ResourcesSaver;
|
||||
|
||||
/**
|
||||
* Jadx API usage example:
|
||||
* <pre><code>
|
||||
*
|
||||
* <pre>
|
||||
* <code>
|
||||
* JadxArgs args = new JadxArgs();
|
||||
* args.getInputFiles().add(new File("test.apk"));
|
||||
* args.setOutDir(new File("jadx-test-output"));
|
||||
@@ -41,14 +53,18 @@ import jadx.core.xmlgen.ResourcesSaver;
|
||||
* JadxDecompiler jadx = new JadxDecompiler(args);
|
||||
* jadx.load();
|
||||
* jadx.save();
|
||||
* </code></pre>
|
||||
*
|
||||
* </code>
|
||||
* </pre>
|
||||
* <p>
|
||||
* Instead of 'save()' you can iterate over decompiled classes:
|
||||
* <pre><code>
|
||||
*
|
||||
* <pre>
|
||||
* <code>
|
||||
* for(JavaClass cls : jadx.getClasses()) {
|
||||
* System.out.println(cls.getCode());
|
||||
* }
|
||||
* </code></pre>
|
||||
* </code>
|
||||
* </pre>
|
||||
*/
|
||||
public final class JadxDecompiler {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(JadxDecompiler.class);
|
||||
@@ -59,7 +75,6 @@ public final class JadxDecompiler {
|
||||
|
||||
private RootNode root;
|
||||
private List<IDexTreeVisitor> passes;
|
||||
private CodeGen codeGen;
|
||||
|
||||
private List<JavaClass> classes;
|
||||
private List<ResourceFile> resources;
|
||||
@@ -91,14 +106,12 @@ public final class JadxDecompiler {
|
||||
|
||||
root.initClassPath();
|
||||
root.loadResources(getResources());
|
||||
root.initAppResClass();
|
||||
|
||||
initVisitors();
|
||||
}
|
||||
|
||||
void init() {
|
||||
this.passes = Jadx.getPassesList(args);
|
||||
this.codeGen = new CodeGen();
|
||||
}
|
||||
|
||||
void reset() {
|
||||
@@ -107,7 +120,6 @@ public final class JadxDecompiler {
|
||||
xmlParser = null;
|
||||
root = null;
|
||||
passes = null;
|
||||
codeGen = null;
|
||||
}
|
||||
|
||||
public static String getVersion() {
|
||||
@@ -176,12 +188,12 @@ public final class JadxDecompiler {
|
||||
sourcesOutDir = args.getOutDirSrc();
|
||||
resOutDir = args.getOutDirRes();
|
||||
}
|
||||
if (saveSources) {
|
||||
appendSourcesSave(executor, sourcesOutDir);
|
||||
}
|
||||
if (saveResources) {
|
||||
appendResourcesSave(executor, resOutDir);
|
||||
}
|
||||
if (saveSources) {
|
||||
appendSourcesSave(executor, sourcesOutDir);
|
||||
}
|
||||
return executor;
|
||||
}
|
||||
|
||||
@@ -192,14 +204,18 @@ public final class JadxDecompiler {
|
||||
}
|
||||
|
||||
private void appendSourcesSave(ExecutorService executor, File outDir) {
|
||||
final Predicate<String> classFilter = args.getClassFilter();
|
||||
for (JavaClass cls : getClasses()) {
|
||||
if (cls.getClassNode().contains(AFlag.DONT_GENERATE)) {
|
||||
continue;
|
||||
}
|
||||
if (classFilter != null && !classFilter.test(cls.getFullName())) {
|
||||
continue;
|
||||
}
|
||||
executor.execute(() -> {
|
||||
try {
|
||||
cls.decompile();
|
||||
SaveCode.save(outDir, args, cls.getClassNode());
|
||||
SaveCode.save(outDir, cls.getClassNode());
|
||||
} catch (Exception e) {
|
||||
LOG.error("Error saving class: {}", cls.getFullName(), e);
|
||||
}
|
||||
@@ -216,9 +232,11 @@ public final class JadxDecompiler {
|
||||
List<JavaClass> clsList = new ArrayList<>(classNodeList.size());
|
||||
classesMap.clear();
|
||||
for (ClassNode classNode : classNodeList) {
|
||||
JavaClass javaClass = new JavaClass(classNode, this);
|
||||
clsList.add(javaClass);
|
||||
classesMap.put(classNode, javaClass);
|
||||
if (!classNode.contains(AFlag.DONT_GENERATE)) {
|
||||
JavaClass javaClass = new JavaClass(classNode, this);
|
||||
clsList.add(javaClass);
|
||||
classesMap.put(classNode, javaClass);
|
||||
}
|
||||
}
|
||||
classes = Collections.unmodifiableList(clsList);
|
||||
}
|
||||
@@ -252,7 +270,7 @@ public final class JadxDecompiler {
|
||||
}
|
||||
Collections.sort(packages);
|
||||
for (JavaPackage pkg : packages) {
|
||||
pkg.getClasses().sort(Comparator.comparing(JavaClass::getName));
|
||||
pkg.getClasses().sort(Comparator.comparing(JavaClass::getName, String.CASE_INSENSITIVE_ORDER));
|
||||
}
|
||||
return Collections.unmodifiableList(packages);
|
||||
}
|
||||
@@ -290,13 +308,41 @@ public final class JadxDecompiler {
|
||||
}
|
||||
|
||||
void processClass(ClassNode cls) {
|
||||
ProcessClass.process(cls, passes, codeGen);
|
||||
ProcessClass.process(cls, passes, true);
|
||||
}
|
||||
|
||||
void generateSmali(ClassNode cls) {
|
||||
Path path = cls.dex().getDexFile().getPath();
|
||||
String className = Utils.makeQualifiedObjectName(cls.getClassInfo().getType().getObject());
|
||||
try {
|
||||
DexBackedDexFile dexFile = DexFileFactory.loadDexFile(path.toFile(), Opcodes.getDefault());
|
||||
boolean decompiled = false;
|
||||
for (DexBackedClassDef classDef : dexFile.getClasses()) {
|
||||
if (classDef.getType().equals(className)) {
|
||||
ClassDefinition classDefinition = new ClassDefinition(new BaksmaliOptions(), classDef);
|
||||
StringWriter sw = new StringWriter();
|
||||
classDefinition.writeTo(new IndentingWriter(sw));
|
||||
cls.setSmali(sw.toString());
|
||||
decompiled = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!decompiled) {
|
||||
LOG.error("Failed to find smali class {}", className);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.error("Error generating smali", e);
|
||||
}
|
||||
}
|
||||
|
||||
RootNode getRoot() {
|
||||
return root;
|
||||
}
|
||||
|
||||
List<IDexTreeVisitor> getPasses() {
|
||||
return passes;
|
||||
}
|
||||
|
||||
synchronized BinaryXMLParser getXmlParser() {
|
||||
if (xmlParser == null) {
|
||||
xmlParser = new BinaryXMLParser(root);
|
||||
@@ -312,10 +358,38 @@ public final class JadxDecompiler {
|
||||
return methodsMap;
|
||||
}
|
||||
|
||||
JavaMethod getJavaMethodByNode(MethodNode mth) {
|
||||
JavaMethod javaMethod = methodsMap.get(mth);
|
||||
if (javaMethod != null) {
|
||||
return javaMethod;
|
||||
}
|
||||
// parent class not loaded yet
|
||||
JavaClass javaClass = classesMap.get(mth.getParentClass());
|
||||
if (javaClass != null) {
|
||||
javaClass.decompile();
|
||||
return methodsMap.get(mth);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Map<FieldNode, JavaField> getFieldsMap() {
|
||||
return fieldsMap;
|
||||
}
|
||||
|
||||
JavaField getJavaFieldByNode(FieldNode fld) {
|
||||
JavaField javaField = fieldsMap.get(fld);
|
||||
if (javaField != null) {
|
||||
return javaField;
|
||||
}
|
||||
// parent class not loaded yet
|
||||
JavaClass javaClass = classesMap.get(fld.getParentClass());
|
||||
if (javaClass != null) {
|
||||
javaClass.decompile();
|
||||
return fieldsMap.get(fld);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public JadxArgs getArgs() {
|
||||
return args;
|
||||
}
|
||||
|
||||
@@ -64,11 +64,21 @@ public final class JavaClass implements JavaNode {
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized String getSmali() {
|
||||
if (decompiler == null) {
|
||||
return null;
|
||||
}
|
||||
if (cls.getSmali() == null) {
|
||||
decompiler.generateSmali(cls);
|
||||
}
|
||||
return cls.getSmali();
|
||||
}
|
||||
|
||||
public synchronized void unload() {
|
||||
cls.unload();
|
||||
}
|
||||
|
||||
ClassNode getClassNode() {
|
||||
public ClassNode getClassNode() {
|
||||
return cls;
|
||||
}
|
||||
|
||||
@@ -160,10 +170,10 @@ public final class JavaClass implements JavaNode {
|
||||
return getRootDecompiler().getClassesMap().get(obj);
|
||||
}
|
||||
if (obj instanceof MethodNode) {
|
||||
return getRootDecompiler().getMethodsMap().get(obj);
|
||||
return getRootDecompiler().getJavaMethodByNode(((MethodNode) obj));
|
||||
}
|
||||
if (obj instanceof FieldNode) {
|
||||
return getRootDecompiler().getFieldsMap().get(obj);
|
||||
return getRootDecompiler().getJavaFieldByNode((FieldNode) obj);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -181,15 +191,6 @@ public final class JavaClass implements JavaNode {
|
||||
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();
|
||||
@@ -265,6 +266,6 @@ public final class JavaClass implements JavaNode {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return cls.getFullName() + "[ " + getFullName() + " ]";
|
||||
return getFullName();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ public final class JavaField implements JavaNode {
|
||||
|
||||
@Override
|
||||
public String getFullName() {
|
||||
return parent.getFullName() + "." + getName();
|
||||
return parent.getFullName() + '.' + getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -39,7 +39,7 @@ public final class JavaField implements JavaNode {
|
||||
}
|
||||
|
||||
public ArgType getType() {
|
||||
return field.getType();
|
||||
return ArgType.tryToResolveClassAlias(field.dex(), field.getType());
|
||||
}
|
||||
|
||||
public int getDecompiledLine() {
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
package jadx.api;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import jadx.core.dex.info.AccessInfo;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
|
||||
public final class JavaMethod implements JavaNode {
|
||||
@@ -40,11 +44,27 @@ public final class JavaMethod implements JavaNode {
|
||||
}
|
||||
|
||||
public List<ArgType> getArguments() {
|
||||
return mth.getMethodInfo().getArgumentsTypes();
|
||||
if (mth.getMethodInfo().getArgumentsTypes().isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<RegisterArg> arguments = mth.getArguments(false);
|
||||
Stream<ArgType> argTypeStream;
|
||||
if (arguments == null || arguments.isEmpty() || mth.isNoCode()) {
|
||||
argTypeStream = mth.getMethodInfo().getArgumentsTypes().stream();
|
||||
} else {
|
||||
argTypeStream = arguments.stream().map(RegisterArg::getType);
|
||||
}
|
||||
return argTypeStream
|
||||
.map(type -> ArgType.tryToResolveClassAlias(mth.dex(), type))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public ArgType getReturnType() {
|
||||
return mth.getReturnType();
|
||||
ArgType retType = mth.getReturnType();
|
||||
if (retType == null) {
|
||||
retType = mth.getMethodInfo().getReturnType();
|
||||
}
|
||||
return ArgType.tryToResolveClassAlias(mth.dex(), retType);
|
||||
}
|
||||
|
||||
public boolean isConstructor() {
|
||||
|
||||
@@ -35,6 +35,13 @@ public class ResourceFile {
|
||||
private final ResourceType type;
|
||||
private ZipRef zipRef;
|
||||
|
||||
public static ResourceFile createResourceFile(JadxDecompiler decompiler, String name, ResourceType type) {
|
||||
if (!ZipSecurity.isValidZipEntryName(name)) {
|
||||
return null;
|
||||
}
|
||||
return new ResourceFile(decompiler, name, type);
|
||||
}
|
||||
|
||||
protected ResourceFile(JadxDecompiler decompiler, String name, ResourceType type) {
|
||||
this.decompiler = decompiler;
|
||||
this.name = name;
|
||||
@@ -63,13 +70,6 @@ public class ResourceFile {
|
||||
|
||||
@Override
|
||||
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);
|
||||
return "ResourceFile{name='" + name + '\'' + ", type=" + type + '}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,27 +1,18 @@
|
||||
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) {
|
||||
public 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);
|
||||
return ResContainer.textResource(getName(), content);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,21 +30,4 @@ public enum ResourceType {
|
||||
}
|
||||
return UNKNOWN;
|
||||
}
|
||||
|
||||
public static boolean isSupportedForUnpack(ResourceType type) {
|
||||
switch (type) {
|
||||
case CODE:
|
||||
case LIB:
|
||||
case FONT:
|
||||
case UNKNOWN:
|
||||
return false;
|
||||
|
||||
case MANIFEST:
|
||||
case XML:
|
||||
case ARSC:
|
||||
case IMG:
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import org.slf4j.LoggerFactory;
|
||||
import jadx.api.ResourceFile.ZipRef;
|
||||
import jadx.core.codegen.CodeWriter;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.android.Res9patchStreamDecoder;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
import jadx.core.utils.files.InputFile;
|
||||
import jadx.core.utils.files.ZipSecurity;
|
||||
@@ -31,8 +32,6 @@ import static jadx.core.utils.files.FileUtils.copyStream;
|
||||
public final class ResourcesLoader {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ResourcesLoader.class);
|
||||
|
||||
private static final int LOAD_SIZE_LIMIT = 10 * 1024 * 1024;
|
||||
|
||||
private final JadxDecompiler jadxRef;
|
||||
|
||||
ResourcesLoader(JadxDecompiler jadxRef) {
|
||||
@@ -47,11 +46,11 @@ public final class ResourcesLoader {
|
||||
return list;
|
||||
}
|
||||
|
||||
public interface ResourceDecoder {
|
||||
ResContainer decode(long size, InputStream is) throws IOException;
|
||||
public interface ResourceDecoder<T> {
|
||||
T decode(long size, InputStream is) throws IOException;
|
||||
}
|
||||
|
||||
public static ResContainer decodeStream(ResourceFile rf, ResourceDecoder decoder) throws JadxException {
|
||||
public static <T> T decodeStream(ResourceFile rf, ResourceDecoder<T> decoder) throws JadxException {
|
||||
try {
|
||||
ZipRef zipRef = rf.getZipRef();
|
||||
if (zipRef == null) {
|
||||
@@ -80,40 +79,50 @@ public final class ResourcesLoader {
|
||||
|
||||
static ResContainer loadContent(JadxDecompiler jadxRef, ResourceFile rf) {
|
||||
try {
|
||||
return decodeStream(rf, (size, is) -> loadContent(jadxRef, rf, is, size));
|
||||
return decodeStream(rf, (size, is) -> loadContent(jadxRef, rf, is));
|
||||
} 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 ResContainer.singleFile(rf.getName(), cw);
|
||||
Utils.appendStackTrace(cw, e.getCause());
|
||||
return ResContainer.textResource(rf.getName(), cw);
|
||||
}
|
||||
}
|
||||
|
||||
private static ResContainer loadContent(JadxDecompiler jadxRef, ResourceFile rf,
|
||||
InputStream inputStream, long size) throws IOException {
|
||||
InputStream inputStream) throws IOException {
|
||||
switch (rf.getType()) {
|
||||
case MANIFEST:
|
||||
case XML:
|
||||
return ResContainer.singleFile(rf.getName(),
|
||||
jadxRef.getXmlParser().parse(inputStream));
|
||||
CodeWriter content = jadxRef.getXmlParser().parse(inputStream);
|
||||
return ResContainer.textResource(rf.getName(), content);
|
||||
|
||||
case ARSC:
|
||||
return new ResTableParser()
|
||||
.decodeFiles(inputStream);
|
||||
return new ResTableParser(jadxRef.getRoot()).decodeFiles(inputStream);
|
||||
|
||||
case IMG:
|
||||
return ResContainer.singleImageFile(rf.getName(), inputStream);
|
||||
return decodeImage(rf, 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 ResContainer.resourceFileLink(rf);
|
||||
}
|
||||
}
|
||||
|
||||
private static ResContainer decodeImage(ResourceFile rf, InputStream inputStream) {
|
||||
String name = rf.getName();
|
||||
if (name.endsWith(".9.png")) {
|
||||
Res9patchStreamDecoder decoder = new Res9patchStreamDecoder();
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
try {
|
||||
decoder.decode(inputStream, os);
|
||||
return ResContainer.decodedData(rf.getName(), os.toByteArray());
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to decode 9-patch png image, path: {}", name, e);
|
||||
}
|
||||
}
|
||||
return ResContainer.resourceFileLink(rf);
|
||||
}
|
||||
|
||||
private void loadFile(List<ResourceFile> list, File file) {
|
||||
if (file == null) {
|
||||
return;
|
||||
@@ -135,7 +144,7 @@ public final class ResourcesLoader {
|
||||
private void addResourceFile(List<ResourceFile> list, File file) {
|
||||
String name = file.getAbsolutePath();
|
||||
ResourceType type = ResourceType.getFileType(name);
|
||||
ResourceFile rf = ResourceFile.createResourceFileInstance(jadxRef, name, type);
|
||||
ResourceFile rf = ResourceFile.createResourceFile(jadxRef, name, type);
|
||||
if (rf != null) {
|
||||
list.add(rf);
|
||||
}
|
||||
@@ -147,7 +156,7 @@ public final class ResourcesLoader {
|
||||
}
|
||||
String name = entry.getName();
|
||||
ResourceType type = ResourceType.getFileType(name);
|
||||
ResourceFile rf = ResourceFile.createResourceFileInstance(jadxRef, name, type);
|
||||
ResourceFile rf = ResourceFile.createResourceFile(jadxRef, name, type);
|
||||
if (rf != null) {
|
||||
rf.setZipRef(new ZipRef(zipFile, name));
|
||||
list.add(rf);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package jadx.core;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Enumeration;
|
||||
@@ -11,36 +12,40 @@ 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;
|
||||
import jadx.core.dex.visitors.DebugInfoVisitor;
|
||||
import jadx.core.dex.visitors.ConstructorVisitor;
|
||||
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.FixAccessModifiers;
|
||||
import jadx.core.dex.visitors.IDexTreeVisitor;
|
||||
import jadx.core.dex.visitors.InitCodeVariables;
|
||||
import jadx.core.dex.visitors.MarkFinallyVisitor;
|
||||
import jadx.core.dex.visitors.MethodInlineVisitor;
|
||||
import jadx.core.dex.visitors.ModVisitor;
|
||||
import jadx.core.dex.visitors.PrepareForCodeGen;
|
||||
import jadx.core.dex.visitors.ProcessAnonymous;
|
||||
import jadx.core.dex.visitors.ReSugarCode;
|
||||
import jadx.core.dex.visitors.RenameVisitor;
|
||||
import jadx.core.dex.visitors.SimplifyVisitor;
|
||||
import jadx.core.dex.visitors.blocksmaker.BlockExceptionHandler;
|
||||
import jadx.core.dex.visitors.blocksmaker.BlockFinallyExtract;
|
||||
import jadx.core.dex.visitors.blocksmaker.BlockFinish;
|
||||
import jadx.core.dex.visitors.blocksmaker.BlockProcessor;
|
||||
import jadx.core.dex.visitors.blocksmaker.BlockSplitter;
|
||||
import jadx.core.dex.visitors.debuginfo.DebugInfoApplyVisitor;
|
||||
import jadx.core.dex.visitors.debuginfo.DebugInfoParseVisitor;
|
||||
import jadx.core.dex.visitors.regions.CheckRegions;
|
||||
import jadx.core.dex.visitors.regions.CleanRegions;
|
||||
import jadx.core.dex.visitors.regions.IfRegionVisitor;
|
||||
import jadx.core.dex.visitors.regions.LoopRegionVisitor;
|
||||
import jadx.core.dex.visitors.regions.ProcessVariables;
|
||||
import jadx.core.dex.visitors.regions.RegionMakerVisitor;
|
||||
import jadx.core.dex.visitors.regions.ReturnVisitor;
|
||||
import jadx.core.dex.visitors.ssa.EliminatePhiNodes;
|
||||
import jadx.core.dex.visitors.regions.variables.ProcessVariables;
|
||||
import jadx.core.dex.visitors.shrink.CodeShrinkVisitor;
|
||||
import jadx.core.dex.visitors.ssa.SSATransform;
|
||||
import jadx.core.dex.visitors.typeinference.FinishTypeInference;
|
||||
import jadx.core.dex.visitors.typeinference.TypeInference;
|
||||
import jadx.core.dex.visitors.typeinference.TypeInferenceVisitor;
|
||||
|
||||
public class Jadx {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(Jadx.class);
|
||||
@@ -59,29 +64,32 @@ public class Jadx {
|
||||
if (args.isFallbackMode()) {
|
||||
passes.add(new FallbackModeVisitor());
|
||||
} else {
|
||||
if (args.isDebugInfo()) {
|
||||
passes.add(new DebugInfoParseVisitor());
|
||||
}
|
||||
|
||||
passes.add(new BlockSplitter());
|
||||
passes.add(new BlockProcessor());
|
||||
passes.add(new BlockExceptionHandler());
|
||||
passes.add(new BlockFinallyExtract());
|
||||
passes.add(new BlockFinish());
|
||||
|
||||
passes.add(new SSATransform());
|
||||
passes.add(new DebugInfoVisitor());
|
||||
passes.add(new TypeInference());
|
||||
|
||||
if (args.isRawCFGOutput()) {
|
||||
passes.add(DotGraphVisitor.dumpRaw());
|
||||
}
|
||||
|
||||
passes.add(new BlockProcessor());
|
||||
passes.add(new BlockExceptionHandler());
|
||||
passes.add(new BlockFinish());
|
||||
|
||||
passes.add(new SSATransform());
|
||||
passes.add(new ConstructorVisitor());
|
||||
passes.add(new InitCodeVariables());
|
||||
passes.add(new MarkFinallyVisitor());
|
||||
passes.add(new ConstInlineVisitor());
|
||||
passes.add(new FinishTypeInference());
|
||||
passes.add(new EliminatePhiNodes());
|
||||
passes.add(new TypeInferenceVisitor());
|
||||
if (args.isDebugInfo()) {
|
||||
passes.add(new DebugInfoApplyVisitor());
|
||||
}
|
||||
|
||||
passes.add(new ModVisitor());
|
||||
|
||||
passes.add(new CodeShrinker());
|
||||
passes.add(new CodeShrinkVisitor());
|
||||
passes.add(new ReSugarCode());
|
||||
|
||||
if (args.isCfgOutput()) {
|
||||
passes.add(DotGraphVisitor.dump());
|
||||
}
|
||||
@@ -89,25 +97,27 @@ public class Jadx {
|
||||
passes.add(new RegionMakerVisitor());
|
||||
passes.add(new IfRegionVisitor());
|
||||
passes.add(new ReturnVisitor());
|
||||
passes.add(new CleanRegions());
|
||||
|
||||
passes.add(new CodeShrinker());
|
||||
passes.add(new CodeShrinkVisitor());
|
||||
passes.add(new SimplifyVisitor());
|
||||
passes.add(new CheckRegions());
|
||||
|
||||
passes.add(new MethodInlineVisitor());
|
||||
passes.add(new ExtractFieldInit());
|
||||
passes.add(new FixAccessModifiers());
|
||||
passes.add(new ProcessAnonymous());
|
||||
passes.add(new ClassModifier());
|
||||
passes.add(new MethodInlineVisitor());
|
||||
passes.add(new EnumVisitor());
|
||||
passes.add(new PrepareForCodeGen());
|
||||
passes.add(new LoopRegionVisitor());
|
||||
passes.add(new ProcessVariables());
|
||||
|
||||
passes.add(new ProcessVariables());
|
||||
passes.add(new PrepareForCodeGen());
|
||||
if (args.isCfgOutput()) {
|
||||
passes.add(DotGraphVisitor.dumpRegions());
|
||||
}
|
||||
|
||||
passes.add(new DependencyCollector());
|
||||
|
||||
passes.add(new RenameVisitor());
|
||||
}
|
||||
return passes;
|
||||
@@ -119,10 +129,12 @@ public class Jadx {
|
||||
if (classLoader != null) {
|
||||
Enumeration<URL> resources = classLoader.getResources("META-INF/MANIFEST.MF");
|
||||
while (resources.hasMoreElements()) {
|
||||
Manifest manifest = new Manifest(resources.nextElement().openStream());
|
||||
String ver = manifest.getMainAttributes().getValue("jadx-version");
|
||||
if (ver != null) {
|
||||
return ver;
|
||||
try (InputStream is = resources.nextElement().openStream()) {
|
||||
Manifest manifest = new Manifest(is);
|
||||
String ver = manifest.getMainAttributes().getValue("jadx-version");
|
||||
if (ver != null) {
|
||||
return ver;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,6 @@ 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;
|
||||
@@ -19,8 +17,8 @@ public final class ProcessClass {
|
||||
private ProcessClass() {
|
||||
}
|
||||
|
||||
public static void process(ClassNode cls, List<IDexTreeVisitor> passes, @Nullable CodeGen codeGen) {
|
||||
if (codeGen == null && cls.getState() == PROCESSED) {
|
||||
public static void process(ClassNode cls, List<IDexTreeVisitor> passes, boolean generateCode) {
|
||||
if (!generateCode && cls.getState() == PROCESSED) {
|
||||
return;
|
||||
}
|
||||
synchronized (getSyncObj(cls)) {
|
||||
@@ -33,9 +31,9 @@ public final class ProcessClass {
|
||||
}
|
||||
cls.setState(PROCESSED);
|
||||
}
|
||||
if (cls.getState() == PROCESSED && codeGen != null) {
|
||||
if (cls.getState() == PROCESSED && generateCode) {
|
||||
processDependencies(cls, passes);
|
||||
codeGen.visit(cls);
|
||||
CodeGen.generate(cls);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
ErrorsCounter.classError(cls, e.getClass().getSimpleName(), e);
|
||||
@@ -48,6 +46,6 @@ public final class ProcessClass {
|
||||
}
|
||||
|
||||
private static void processDependencies(ClassNode cls, List<IDexTreeVisitor> passes) {
|
||||
cls.getDependencies().forEach(depCls -> process(depCls, passes, null));
|
||||
cls.getDependencies().forEach(depCls -> process(depCls, passes, false));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,10 +5,12 @@ import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
@@ -21,15 +23,15 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
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
|
||||
*/
|
||||
@@ -41,10 +43,16 @@ public class ClsSet {
|
||||
private static final String CLST_PKG_PATH = ClsSet.class.getPackage().getName().replace('.', '/');
|
||||
|
||||
private static final String JADX_CLS_SET_HEADER = "jadx-cst";
|
||||
private static final int VERSION = 1;
|
||||
private static final int VERSION = 2;
|
||||
|
||||
private static final String STRING_CHARSET = "US-ASCII";
|
||||
|
||||
private static final NClass[] EMPTY_NCLASS_ARRAY = new NClass[0];
|
||||
|
||||
private enum TypeEnum {
|
||||
WILDCARD, GENERIC, GENERIC_TYPE, OBJECT, ARRAY, PRIMITIVE
|
||||
}
|
||||
|
||||
private NClass[] classes;
|
||||
|
||||
public void load(RootNode root) {
|
||||
@@ -54,11 +62,13 @@ public class ClsSet {
|
||||
for (ClassNode cls : list) {
|
||||
String clsRawName = cls.getRawName();
|
||||
if (cls.getAccessFlags().isPublic()) {
|
||||
cls.load();
|
||||
NClass nClass = new NClass(clsRawName, k);
|
||||
if (names.put(clsRawName, nClass) != null) {
|
||||
throw new JadxRuntimeException("Duplicate class: " + clsRawName);
|
||||
}
|
||||
k++;
|
||||
nClass.setMethods(loadMethods(cls, nClass));
|
||||
} else {
|
||||
names.put(clsRawName, null);
|
||||
}
|
||||
@@ -78,6 +88,47 @@ public class ClsSet {
|
||||
}
|
||||
}
|
||||
|
||||
private NMethod[] loadMethods(ClassNode cls, NClass nClass) {
|
||||
List<NMethod> methods = new ArrayList<>();
|
||||
for (MethodNode m : cls.getMethods()) {
|
||||
if (!m.getAccessFlags().isPublic()
|
||||
&& !m.getAccessFlags().isProtected()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
List<ArgType> args = new ArrayList<>();
|
||||
|
||||
boolean genericArg = false;
|
||||
for (RegisterArg r : m.getArguments(false)) {
|
||||
ArgType argType = r.getType();
|
||||
if (argType.isGeneric() || argType.isGenericType()) {
|
||||
args.add(argType);
|
||||
genericArg = true;
|
||||
} else {
|
||||
args.add(null);
|
||||
}
|
||||
}
|
||||
|
||||
ArgType retType = m.getReturnType();
|
||||
if (!retType.isGeneric() && !retType.isGenericType()) {
|
||||
retType = null;
|
||||
}
|
||||
|
||||
boolean varArgs = m.getAccessFlags().isVarArgs();
|
||||
|
||||
if (genericArg || retType != null || varArgs) {
|
||||
methods.add(new NMethod(
|
||||
m.getMethodInfo().getShortId(),
|
||||
args.isEmpty()
|
||||
? new ArgType[0]
|
||||
: args.toArray(new ArgType[args.size()]),
|
||||
retType,
|
||||
varArgs));
|
||||
}
|
||||
}
|
||||
return methods.toArray(new NMethod[methods.size()]);
|
||||
}
|
||||
|
||||
public static NClass[] makeParentsArray(ClassNode cls, Map<String, NClass> names) {
|
||||
List<NClass> parents = new ArrayList<>(1 + cls.getInterfaces().size());
|
||||
ArgType superClass = cls.getSuperClass();
|
||||
@@ -93,54 +144,142 @@ public class ClsSet {
|
||||
parents.add(c);
|
||||
}
|
||||
}
|
||||
return parents.toArray(new NClass[parents.size()]);
|
||||
int size = parents.size();
|
||||
if (size == 0) {
|
||||
return EMPTY_NCLASS_ARRAY;
|
||||
}
|
||||
return parents.toArray(new NClass[size]);
|
||||
}
|
||||
|
||||
private static NClass getCls(String fullName, Map<String, NClass> names) {
|
||||
NClass id = names.get(fullName);
|
||||
if (id == null && !names.containsKey(fullName)) {
|
||||
NClass cls = names.get(fullName);
|
||||
if (cls == null) {
|
||||
LOG.debug("Class not found: {}", fullName);
|
||||
}
|
||||
return id;
|
||||
return cls;
|
||||
}
|
||||
|
||||
void save(File output) throws IOException {
|
||||
FileUtils.makeDirsForFile(output);
|
||||
try (BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(output))) {
|
||||
String outputName = output.getName();
|
||||
if (outputName.endsWith(CLST_EXTENSION)) {
|
||||
void save(Path path) throws IOException {
|
||||
FileUtils.makeDirsForFile(path);
|
||||
String outputName = path.getFileName().toString();
|
||||
if (outputName.endsWith(CLST_EXTENSION)) {
|
||||
try (BufferedOutputStream outputStream = new BufferedOutputStream(Files.newOutputStream(path))) {
|
||||
save(outputStream);
|
||||
} else if (outputName.endsWith(".jar")) {
|
||||
ZipOutputStream out = new ZipOutputStream(outputStream);
|
||||
try {
|
||||
out.putNextEntry(new ZipEntry(CLST_PKG_PATH + "/" + CLST_FILENAME));
|
||||
save(out);
|
||||
} finally {
|
||||
close(out);
|
||||
}
|
||||
} else {
|
||||
throw new JadxRuntimeException("Unknown file format: " + outputName);
|
||||
}
|
||||
} else if (outputName.endsWith(".jar")) {
|
||||
Path temp = FileUtils.createTempFile(".zip");
|
||||
Files.copy(path, temp, StandardCopyOption.REPLACE_EXISTING);
|
||||
|
||||
try (ZipOutputStream out = new ZipOutputStream(Files.newOutputStream(path));
|
||||
ZipInputStream in = new ZipInputStream(Files.newInputStream(temp))) {
|
||||
String clst = CLST_PKG_PATH + '/' + CLST_FILENAME;
|
||||
out.putNextEntry(new ZipEntry(clst));
|
||||
save(out);
|
||||
ZipEntry entry = in.getNextEntry();
|
||||
while (entry != null) {
|
||||
if (!entry.getName().equals(clst)) {
|
||||
out.putNextEntry(new ZipEntry(entry.getName()));
|
||||
FileUtils.copyStream(in, out);
|
||||
}
|
||||
entry = in.getNextEntry();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new JadxRuntimeException("Unknown file format: " + outputName);
|
||||
}
|
||||
}
|
||||
|
||||
public void save(OutputStream output) throws IOException {
|
||||
try (DataOutputStream out = new DataOutputStream(output)) {
|
||||
out.writeBytes(JADX_CLS_SET_HEADER);
|
||||
out.writeByte(VERSION);
|
||||
DataOutputStream out = new DataOutputStream(output);
|
||||
out.writeBytes(JADX_CLS_SET_HEADER);
|
||||
out.writeByte(VERSION);
|
||||
|
||||
LOG.info("Classes count: {}", classes.length);
|
||||
out.writeInt(classes.length);
|
||||
for (NClass cls : classes) {
|
||||
writeString(out, cls.getName());
|
||||
LOG.info("Classes count: {}", classes.length);
|
||||
Map<String, NClass> names = new HashMap<>(classes.length);
|
||||
out.writeInt(classes.length);
|
||||
for (NClass cls : classes) {
|
||||
writeString(out, cls.getName());
|
||||
names.put(cls.getName(), cls);
|
||||
}
|
||||
for (NClass cls : classes) {
|
||||
NClass[] parents = cls.getParents();
|
||||
out.writeByte(parents.length);
|
||||
for (NClass parent : parents) {
|
||||
out.writeInt(parent.getId());
|
||||
}
|
||||
for (NClass cls : classes) {
|
||||
NClass[] parents = cls.getParents();
|
||||
out.writeByte(parents.length);
|
||||
for (NClass parent : parents) {
|
||||
out.writeInt(parent.getId());
|
||||
NMethod[] methods = cls.getMethods();
|
||||
out.writeByte(methods.length);
|
||||
for (NMethod method : methods) {
|
||||
writeMethod(out, method, names);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void writeMethod(DataOutputStream out, NMethod method, Map<String, NClass> names) throws IOException {
|
||||
int argCount = 0;
|
||||
ArgType[] argTypes = method.getArgType();
|
||||
for (ArgType arg : argTypes) {
|
||||
if (arg != null) {
|
||||
argCount++;
|
||||
}
|
||||
}
|
||||
|
||||
writeLongString(out, method.getShortId());
|
||||
out.writeByte(argCount);
|
||||
|
||||
// last argument first
|
||||
for (int i = argTypes.length - 1; i >= 0; i--) {
|
||||
ArgType argType = argTypes[i];
|
||||
if (argType != null) {
|
||||
out.writeByte(i);
|
||||
writeArgType(out, argType, names);
|
||||
}
|
||||
}
|
||||
|
||||
if (method.getReturnType() != null) {
|
||||
out.writeBoolean(true);
|
||||
writeArgType(out, method.getReturnType(), names);
|
||||
} else {
|
||||
out.writeBoolean(false);
|
||||
}
|
||||
|
||||
out.writeBoolean(method.isVarArgs());
|
||||
}
|
||||
|
||||
private static void writeArgType(DataOutputStream out, ArgType argType, Map<String, NClass> names) throws IOException {
|
||||
if (argType.getWildcardType() != null) {
|
||||
out.writeByte(TypeEnum.WILDCARD.ordinal());
|
||||
int bounds = argType.getWildcardBounds();
|
||||
out.writeByte(bounds);
|
||||
if (bounds != 0) {
|
||||
writeArgType(out, argType.getWildcardType(), names);
|
||||
}
|
||||
} else if (argType.isGeneric()) {
|
||||
out.writeByte(TypeEnum.GENERIC.ordinal());
|
||||
out.writeInt(names.get(argType.getObject()).getId());
|
||||
ArgType[] types = argType.getGenericTypes();
|
||||
if (types == null) {
|
||||
out.writeByte(0);
|
||||
} else {
|
||||
out.writeByte(types.length);
|
||||
for (ArgType type : types) {
|
||||
writeArgType(out, type, names);
|
||||
}
|
||||
}
|
||||
} else if (argType.isGenericType()) {
|
||||
out.writeByte(TypeEnum.GENERIC_TYPE.ordinal());
|
||||
writeString(out, argType.getObject());
|
||||
} else if (argType.isObject()) {
|
||||
out.writeByte(TypeEnum.OBJECT.ordinal());
|
||||
out.writeInt(names.get(argType.getObject()).getId());
|
||||
} else if (argType.isArray()) {
|
||||
out.writeByte(TypeEnum.ARRAY.ordinal());
|
||||
writeArgType(out, argType.getArrayElement(), names);
|
||||
} else if (argType.isPrimitive()) {
|
||||
out.writeByte(TypeEnum.PRIMITIVE.ordinal());
|
||||
out.writeByte(argType.getPrimitiveType().getShortName().charAt(0));
|
||||
} else {
|
||||
throw new JadxRuntimeException("Cannot save type: " + argType);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,18 +336,111 @@ public class ClsSet {
|
||||
parents[j] = classes[in.readInt()];
|
||||
}
|
||||
classes[i].setParents(parents);
|
||||
|
||||
int mCount = in.readByte();
|
||||
NMethod[] methods = new NMethod[mCount];
|
||||
for (int j = 0; j < mCount; j++) {
|
||||
methods[j] = readMethod(in);
|
||||
}
|
||||
classes[i].setMethods(methods);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void writeString(DataOutputStream out, String name) throws IOException {
|
||||
private NMethod readMethod(DataInputStream in) throws IOException {
|
||||
String shortId = readLongString(in);
|
||||
int argCount = in.readByte();
|
||||
ArgType[] argTypes = null;
|
||||
for (int i = 0; i < argCount; i++) {
|
||||
int index = in.readByte();
|
||||
ArgType argType = readArgType(in);
|
||||
if (argTypes == null) {
|
||||
argTypes = new ArgType[index + 1];
|
||||
}
|
||||
argTypes[index] = argType;
|
||||
}
|
||||
ArgType retType = in.readBoolean() ? readArgType(in) : null;
|
||||
boolean varArgs = in.readBoolean();
|
||||
return new NMethod(shortId, argTypes, retType, varArgs);
|
||||
}
|
||||
|
||||
private ArgType readArgType(DataInputStream in) throws IOException {
|
||||
int ordinal = in.readByte();
|
||||
switch (TypeEnum.values()[ordinal]) {
|
||||
case WILDCARD:
|
||||
int bounds = in.readByte();
|
||||
return bounds == 0
|
||||
? ArgType.wildcard()
|
||||
: ArgType.wildcard(readArgType(in), bounds);
|
||||
case GENERIC:
|
||||
String obj = classes[in.readInt()].getName();
|
||||
int typeLength = in.readByte();
|
||||
ArgType[] generics;
|
||||
if (typeLength == 0) {
|
||||
generics = null;
|
||||
} else {
|
||||
generics = new ArgType[typeLength];
|
||||
for (int i = 0; i < typeLength; i++) {
|
||||
generics[i] = readArgType(in);
|
||||
}
|
||||
}
|
||||
return ArgType.generic(obj, generics);
|
||||
case GENERIC_TYPE:
|
||||
return ArgType.genericType(readString(in));
|
||||
case OBJECT:
|
||||
return ArgType.object(classes[in.readInt()].getName());
|
||||
case ARRAY:
|
||||
return ArgType.array(readArgType(in));
|
||||
case PRIMITIVE:
|
||||
int shortName = in.readByte();
|
||||
switch (shortName) {
|
||||
case 'Z':
|
||||
return ArgType.BOOLEAN;
|
||||
case 'C':
|
||||
return ArgType.CHAR;
|
||||
case 'B':
|
||||
return ArgType.BYTE;
|
||||
case 'S':
|
||||
return ArgType.SHORT;
|
||||
case 'I':
|
||||
return ArgType.INT;
|
||||
case 'F':
|
||||
return ArgType.FLOAT;
|
||||
case 'J':
|
||||
return ArgType.LONG;
|
||||
case 'D':
|
||||
return ArgType.DOUBLE;
|
||||
default:
|
||||
return ArgType.VOID;
|
||||
}
|
||||
default:
|
||||
throw new JadxRuntimeException("Unsupported Arg Type: " + ordinal);
|
||||
}
|
||||
}
|
||||
|
||||
private static void writeString(DataOutputStream out, String name) throws IOException {
|
||||
byte[] bytes = name.getBytes(STRING_CHARSET);
|
||||
out.writeByte(bytes.length);
|
||||
out.write(bytes);
|
||||
}
|
||||
|
||||
private static void writeLongString(DataOutputStream out, String name) throws IOException {
|
||||
byte[] bytes = name.getBytes(STRING_CHARSET);
|
||||
out.writeShort(bytes.length);
|
||||
out.write(bytes);
|
||||
}
|
||||
|
||||
private static String readString(DataInputStream in) throws IOException {
|
||||
int len = in.readByte();
|
||||
return readString(in, len);
|
||||
}
|
||||
|
||||
private static String readLongString(DataInputStream in) throws IOException {
|
||||
int len = in.readShort();
|
||||
return readString(in, len);
|
||||
}
|
||||
|
||||
private static String readString(DataInputStream in, int len) throws IOException {
|
||||
byte[] bytes = new byte[len];
|
||||
int count = in.read(bytes);
|
||||
while (count != len) {
|
||||
|
||||
@@ -23,7 +23,7 @@ import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
public class ClspGraph {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ClspGraph.class);
|
||||
|
||||
private final Map<String, Set<String>> ancestorCache = Collections.synchronizedMap(new WeakHashMap<String, Set<String>>());
|
||||
private final Map<String, Set<String>> ancestorCache = Collections.synchronizedMap(new WeakHashMap<>());
|
||||
private Map<String, NClass> nameMap;
|
||||
|
||||
private final Set<String> missingClasses = new HashSet<>();
|
||||
@@ -58,6 +58,10 @@ public class ClspGraph {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isClsKnown(String fullName) {
|
||||
return nameMap.containsKey(fullName);
|
||||
}
|
||||
|
||||
private NClass addClass(ClassNode cls) {
|
||||
String rawName = cls.getRawName();
|
||||
NClass nClass = new NClass(rawName, -1);
|
||||
@@ -65,11 +69,24 @@ public class ClspGraph {
|
||||
return nClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@code clsName} instanceof {@code implClsName}
|
||||
*/
|
||||
public boolean isImplements(String clsName, String implClsName) {
|
||||
Set<String> anc = getAncestors(clsName);
|
||||
return anc.contains(implClsName);
|
||||
}
|
||||
|
||||
public List<String> getImplementations(String clsName) {
|
||||
List<String> list = new ArrayList<>();
|
||||
for (String cls : nameMap.keySet()) {
|
||||
if (isImplements(cls, clsName)) {
|
||||
list.add(cls);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
public String getCommonAncestor(String clsName, String implClsName) {
|
||||
if (clsName.equals(implClsName)) {
|
||||
return clsName;
|
||||
@@ -100,7 +117,7 @@ public class ClspGraph {
|
||||
return null;
|
||||
}
|
||||
|
||||
private Set<String> getAncestors(String clsName) {
|
||||
public Set<String> getAncestors(String clsName) {
|
||||
Set<String> result = ancestorCache.get(clsName);
|
||||
if (result != null) {
|
||||
return result;
|
||||
|
||||
@@ -2,6 +2,8 @@ package jadx.core.clsp;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -28,7 +30,7 @@ public class ConvertToClsSet {
|
||||
usage();
|
||||
System.exit(1);
|
||||
}
|
||||
File output = new File(args[0]);
|
||||
Path output = Paths.get(args[0]);
|
||||
|
||||
List<InputFile> inputFiles = new ArrayList<>(args.length - 1);
|
||||
for (int i = 1; i < args.length; i++) {
|
||||
@@ -36,7 +38,7 @@ public class ConvertToClsSet {
|
||||
if (f.isDirectory()) {
|
||||
addFilesFromDirectory(f, inputFiles);
|
||||
} else {
|
||||
InputFile.addFilesFrom(f, inputFiles);
|
||||
InputFile.addFilesFrom(f, inputFiles, false);
|
||||
}
|
||||
}
|
||||
for (InputFile inputFile : inputFiles) {
|
||||
@@ -63,7 +65,7 @@ public class ConvertToClsSet {
|
||||
addFilesFromDirectory(file, inputFiles);
|
||||
} else {
|
||||
try {
|
||||
InputFile.addFilesFrom(file, inputFiles);
|
||||
InputFile.addFilesFrom(file, inputFiles, false);
|
||||
} catch (Exception e) {
|
||||
LOG.warn("Skip file: {}, load error: {}", file, e.getMessage());
|
||||
}
|
||||
|
||||
@@ -7,7 +7,8 @@ public class NClass {
|
||||
|
||||
private final String name;
|
||||
private NClass[] parents;
|
||||
private int id;
|
||||
private NMethod[] methods;
|
||||
private final int id;
|
||||
|
||||
public NClass(String name, int id) {
|
||||
this.name = name;
|
||||
@@ -22,10 +23,6 @@ public class NClass {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public NClass[] getParents() {
|
||||
return parents;
|
||||
}
|
||||
@@ -55,4 +52,12 @@ public class NClass {
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setMethods(NMethod[] methods) {
|
||||
this.methods = methods;
|
||||
}
|
||||
|
||||
public NMethod[] getMethods() {
|
||||
return methods;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package jadx.core.clsp;
|
||||
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
|
||||
/**
|
||||
* Generic method node in classpath graph.
|
||||
*/
|
||||
public class NMethod {
|
||||
|
||||
private final String shortId;
|
||||
private final ArgType[] argType;
|
||||
private final ArgType retType;
|
||||
private final boolean varArgs;
|
||||
|
||||
public NMethod(String shortId, ArgType[] argType, ArgType retType, boolean varArgs) {
|
||||
this.shortId = shortId;
|
||||
this.argType = argType;
|
||||
this.retType = retType;
|
||||
this.varArgs = varArgs;
|
||||
}
|
||||
|
||||
public String getShortId() {
|
||||
return shortId;
|
||||
}
|
||||
|
||||
public ArgType[] getArgType() {
|
||||
return argType;
|
||||
}
|
||||
|
||||
public ArgType getReturnType() {
|
||||
return retType;
|
||||
}
|
||||
|
||||
public boolean isVarArgs() {
|
||||
return varArgs;
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,8 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.core.Consts;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.IAttributeNode;
|
||||
@@ -63,12 +65,7 @@ public class AnnotationGen {
|
||||
}
|
||||
for (Annotation a : aList.getAll()) {
|
||||
String aCls = a.getAnnotationClass();
|
||||
if (aCls.startsWith(Consts.DALVIK_ANNOTATION_PKG)) {
|
||||
// skip
|
||||
if (Consts.DEBUG) {
|
||||
code.startLine("// " + a);
|
||||
}
|
||||
} else {
|
||||
if (!aCls.startsWith(Consts.DALVIK_ANNOTATION_PKG)) {
|
||||
code.startLine();
|
||||
formatAnnotation(code, a);
|
||||
}
|
||||
@@ -77,34 +74,52 @@ public class AnnotationGen {
|
||||
|
||||
private void formatAnnotation(CodeWriter code, Annotation a) {
|
||||
code.add('@');
|
||||
classGen.useType(code, a.getType());
|
||||
ClassNode annCls = cls.dex().resolveClass(a.getType());
|
||||
if (annCls != null) {
|
||||
classGen.useClass(code, annCls);
|
||||
} else {
|
||||
classGen.useType(code, a.getType());
|
||||
}
|
||||
|
||||
Map<String, Object> vl = a.getValues();
|
||||
if (!vl.isEmpty()) {
|
||||
code.add('(');
|
||||
if (vl.size() == 1 && vl.containsKey("value")) {
|
||||
encodeValue(code, vl.get("value"));
|
||||
} else {
|
||||
for (Iterator<Entry<String, Object>> it = vl.entrySet().iterator(); it.hasNext(); ) {
|
||||
Entry<String, Object> e = it.next();
|
||||
code.add(e.getKey());
|
||||
for (Iterator<Entry<String, Object>> it = vl.entrySet().iterator(); it.hasNext();) {
|
||||
Entry<String, Object> e = it.next();
|
||||
String paramName = getParamName(annCls, e.getKey());
|
||||
if (paramName.equals("value") && vl.size() == 1) {
|
||||
// don't add "value = " if no other parameters
|
||||
} else {
|
||||
code.add(paramName);
|
||||
code.add(" = ");
|
||||
encodeValue(code, e.getValue());
|
||||
if (it.hasNext()) {
|
||||
code.add(", ");
|
||||
}
|
||||
}
|
||||
encodeValue(code, e.getValue());
|
||||
if (it.hasNext()) {
|
||||
code.add(", ");
|
||||
}
|
||||
}
|
||||
code.add(')');
|
||||
}
|
||||
}
|
||||
|
||||
private String getParamName(@Nullable ClassNode annCls, String paramName) {
|
||||
if (annCls != null) {
|
||||
// TODO: save value type and search using signature
|
||||
MethodNode mth = annCls.searchMethodByShortName(paramName);
|
||||
if (mth != null) {
|
||||
return mth.getAlias();
|
||||
}
|
||||
}
|
||||
return paramName;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void addThrows(MethodNode mth, CodeWriter code) {
|
||||
Annotation an = mth.getAnnotation(Consts.DALVIK_THROWS);
|
||||
if (an != null) {
|
||||
Object exs = an.getDefaultValue();
|
||||
code.add(" throws ");
|
||||
for (Iterator<ArgType> it = ((List<ArgType>) exs).iterator(); it.hasNext(); ) {
|
||||
for (Iterator<ArgType> it = ((List<ArgType>) exs).iterator(); it.hasNext();) {
|
||||
ArgType ex = it.next();
|
||||
classGen.useType(code, ex);
|
||||
if (it.hasNext()) {
|
||||
@@ -156,7 +171,7 @@ public class AnnotationGen {
|
||||
InsnGen.makeStaticFieldAccess(code, field, classGen);
|
||||
} else if (val instanceof Iterable) {
|
||||
code.add('{');
|
||||
Iterator<?> it = ((Iterable) val).iterator();
|
||||
Iterator<?> it = ((Iterable<?>) val).iterator();
|
||||
while (it.hasNext()) {
|
||||
Object obj = it.next();
|
||||
encodeValue(code, obj);
|
||||
@@ -169,7 +184,7 @@ public class AnnotationGen {
|
||||
formatAnnotation(code, (Annotation) val);
|
||||
} else {
|
||||
// TODO: also can be method values
|
||||
throw new JadxRuntimeException("Can't decode value: " + val + " (" + val.getClass() + ")");
|
||||
throw new JadxRuntimeException("Can't decode value: " + val + " (" + val.getClass() + ')');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package jadx.core.codegen;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
@@ -19,9 +18,7 @@ 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;
|
||||
@@ -34,9 +31,11 @@ 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.CodeGenUtils;
|
||||
import jadx.core.utils.ErrorsCounter;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.CodegenException;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
public class ClassGen {
|
||||
|
||||
@@ -83,31 +82,30 @@ public class ClassGen {
|
||||
}
|
||||
int importsCount = imports.size();
|
||||
if (importsCount != 0) {
|
||||
List<String> sortImports = new ArrayList<>(importsCount);
|
||||
for (ClassInfo ic : imports) {
|
||||
sortImports.add(ic.getAlias().getFullName());
|
||||
}
|
||||
Collections.sort(sortImports);
|
||||
|
||||
for (String imp : sortImports) {
|
||||
clsCode.startLine("import ").add(imp).add(';');
|
||||
}
|
||||
List<ClassInfo> sortedImports = new ArrayList<>(imports);
|
||||
sortedImports.sort(Comparator.comparing(ClassInfo::getAliasFullName));
|
||||
sortedImports.forEach(classInfo -> {
|
||||
clsCode.startLine("import ");
|
||||
ClassNode classNode = cls.root().resolveClass(classInfo);
|
||||
if (classNode != null) {
|
||||
clsCode.attachAnnotation(classNode);
|
||||
}
|
||||
clsCode.add(classInfo.getAliasFullName());
|
||||
clsCode.add(';');
|
||||
});
|
||||
clsCode.newLine();
|
||||
|
||||
sortImports.clear();
|
||||
imports.clear();
|
||||
}
|
||||
clsCode.add(clsBody);
|
||||
return clsCode;
|
||||
return clsCode.finish();
|
||||
}
|
||||
|
||||
public void addClassCode(CodeWriter code) throws CodegenException {
|
||||
if (cls.contains(AFlag.DONT_GENERATE)) {
|
||||
return;
|
||||
}
|
||||
if (cls.contains(AFlag.INCONSISTENT_CODE)) {
|
||||
code.startLine("// jadx: inconsistent code");
|
||||
}
|
||||
CodeGenUtils.addComments(code, cls);
|
||||
insertDecompilationProblems(code, cls);
|
||||
addClassDeclaration(code);
|
||||
addClassBody(code);
|
||||
}
|
||||
@@ -124,13 +122,13 @@ public class ClassGen {
|
||||
}
|
||||
|
||||
// 'static' and 'private' modifier not allowed for top classes (not inner)
|
||||
if (!cls.getAlias().isInner()) {
|
||||
if (!cls.getClassInfo().isInner()) {
|
||||
af = af.remove(AccessFlags.ACC_STATIC).remove(AccessFlags.ACC_PRIVATE);
|
||||
}
|
||||
|
||||
annotationGen.addForClass(clsCode);
|
||||
insertSourceFileInfo(clsCode, cls);
|
||||
insertRenameInfo(clsCode, cls);
|
||||
CodeGenUtils.addSourceFileInfo(clsCode, cls);
|
||||
clsCode.startLine(af.makeString());
|
||||
if (af.isInterface()) {
|
||||
if (af.isAnnotation()) {
|
||||
@@ -143,9 +141,9 @@ public class ClassGen {
|
||||
clsCode.add("class ");
|
||||
}
|
||||
clsCode.attachDefinition(cls);
|
||||
clsCode.add(cls.getShortName());
|
||||
clsCode.add(cls.getClassInfo().getAliasShortName());
|
||||
|
||||
addGenericMap(clsCode, cls.getGenericMap());
|
||||
addGenericMap(clsCode, cls.getGenericMap(), true);
|
||||
clsCode.add(' ');
|
||||
|
||||
ArgType sup = cls.getSuperClass();
|
||||
@@ -163,7 +161,7 @@ public class ClassGen {
|
||||
} else {
|
||||
clsCode.add("implements ");
|
||||
}
|
||||
for (Iterator<ArgType> it = cls.getInterfaces().iterator(); it.hasNext(); ) {
|
||||
for (Iterator<ArgType> it = cls.getInterfaces().iterator(); it.hasNext();) {
|
||||
ArgType interf = it.next();
|
||||
useClass(clsCode, interf);
|
||||
if (it.hasNext()) {
|
||||
@@ -176,7 +174,7 @@ public class ClassGen {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean addGenericMap(CodeWriter code, Map<ArgType, List<ArgType>> gmap) {
|
||||
public boolean addGenericMap(CodeWriter code, Map<ArgType, List<ArgType>> gmap, boolean classDeclaration) {
|
||||
if (gmap == null || gmap.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
@@ -195,12 +193,17 @@ public class ClassGen {
|
||||
}
|
||||
if (list != null && !list.isEmpty()) {
|
||||
code.add(" extends ");
|
||||
for (Iterator<ArgType> it = list.iterator(); it.hasNext(); ) {
|
||||
for (Iterator<ArgType> it = list.iterator(); it.hasNext();) {
|
||||
ArgType g = it.next();
|
||||
if (g.isGenericType()) {
|
||||
code.add(g.getObject());
|
||||
} else {
|
||||
useClass(code, g);
|
||||
if (classDeclaration
|
||||
&& !cls.getClassInfo().isInner()
|
||||
&& cls.root().getArgs().isUseImports()) {
|
||||
addImport(ClassInfo.fromType(cls.root(), g));
|
||||
}
|
||||
}
|
||||
if (it.hasNext()) {
|
||||
code.add(" & ");
|
||||
@@ -226,8 +229,7 @@ public class ClassGen {
|
||||
|
||||
private void addInnerClasses(CodeWriter code, ClassNode cls) throws CodegenException {
|
||||
for (ClassNode innerCls : cls.getInnerClasses()) {
|
||||
if (innerCls.contains(AFlag.DONT_GENERATE)
|
||||
|| innerCls.contains(AFlag.ANONYMOUS_CLASS)) {
|
||||
if (innerCls.contains(AFlag.DONT_GENERATE)) {
|
||||
continue;
|
||||
}
|
||||
ClassGen inClGen = new ClassGen(innerCls, getParentGen());
|
||||
@@ -259,11 +261,15 @@ public class ClassGen {
|
||||
try {
|
||||
addMethod(code, mth);
|
||||
} catch (Exception e) {
|
||||
if (mth.getParentClass().getTopParentClass().contains(AFlag.RESTART_CODEGEN)) {
|
||||
throw new JadxRuntimeException("Method generation error", e);
|
||||
}
|
||||
code.newLine().add("/*");
|
||||
code.newLine().add(ErrorsCounter.methodError(mth, "Method generation error", e));
|
||||
code.newLine().add(Utils.getStackTrace(e));
|
||||
code.newLine().addMultiLine(ErrorsCounter.methodError(mth, "Method generation error", e));
|
||||
Utils.appendStackTrace(code, e);
|
||||
code.newLine().add("*/");
|
||||
code.setIndent(savedIndent);
|
||||
mth.addError("Method generation error: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -283,30 +289,21 @@ public class ClassGen {
|
||||
return false;
|
||||
}
|
||||
|
||||
private void addMethod(CodeWriter code, MethodNode mth) throws CodegenException {
|
||||
public void addMethod(CodeWriter code, MethodNode mth) throws CodegenException {
|
||||
CodeGenUtils.addComments(code, mth);
|
||||
if (mth.getAccessFlags().isAbstract() || mth.getAccessFlags().isNative()) {
|
||||
MethodGen mthGen = new MethodGen(this, mth);
|
||||
mthGen.addDefinition(code);
|
||||
if (cls.getAccessFlags().isAnnotation()) {
|
||||
Object def = annotationGen.getAnnotationDefaultValue(mth.getName());
|
||||
if (def != null) {
|
||||
code.add(" default ");
|
||||
annotationGen.encodeValue(code, def);
|
||||
}
|
||||
}
|
||||
code.add(';');
|
||||
} else {
|
||||
insertDecompilationProblems(code, mth);
|
||||
boolean badCode = mth.contains(AFlag.INCONSISTENT_CODE);
|
||||
if (badCode) {
|
||||
if (showInconsistentCode) {
|
||||
code.startLine("/* Code decompiled incorrectly, please refer to instructions dump. */");
|
||||
mth.remove(AFlag.INCONSISTENT_CODE);
|
||||
badCode = false;
|
||||
}
|
||||
if (badCode && showInconsistentCode) {
|
||||
mth.remove(AFlag.INCONSISTENT_CODE);
|
||||
badCode = false;
|
||||
}
|
||||
MethodGen mthGen;
|
||||
if (badCode || mth.contains(AType.JADX_ERROR) || fallback) {
|
||||
if (badCode || fallback || mth.contains(AType.JADX_ERROR) || mth.getRegion() == null) {
|
||||
mthGen = MethodGen.getFallbackMethodGen(mth);
|
||||
} else {
|
||||
mthGen = new MethodGen(this, mth);
|
||||
@@ -316,22 +313,16 @@ public class ClassGen {
|
||||
}
|
||||
code.add('{');
|
||||
code.incIndent();
|
||||
insertSourceFileInfo(code, mth);
|
||||
if (fallback) {
|
||||
mthGen.addFallbackMethodCode(code);
|
||||
} else {
|
||||
mthGen.addInstructions(code);
|
||||
}
|
||||
mthGen.addInstructions(code);
|
||||
code.decIndent();
|
||||
code.startLine('}');
|
||||
}
|
||||
}
|
||||
|
||||
private void insertDecompilationProblems(CodeWriter code, MethodNode mth) {
|
||||
List<JadxError> errors = mth.getAll(AType.JADX_ERROR);
|
||||
List<JadxWarn> warns = mth.getAll(AType.JADX_WARN);
|
||||
public void insertDecompilationProblems(CodeWriter code, AttrNode node) {
|
||||
List<JadxError> errors = node.getAll(AType.JADX_ERROR);
|
||||
if (!errors.isEmpty()) {
|
||||
errors.forEach(err -> {
|
||||
errors.stream().distinct().sorted().forEach(err -> {
|
||||
code.startLine("/* JADX ERROR: ").add(err.getError());
|
||||
Throwable cause = err.getCause();
|
||||
if (cause != null) {
|
||||
@@ -342,43 +333,51 @@ public class ClassGen {
|
||||
code.add("*/");
|
||||
});
|
||||
}
|
||||
List<String> warns = node.getAll(AType.JADX_WARN);
|
||||
if (!warns.isEmpty()) {
|
||||
warns.forEach(warn -> code.startLine("/* JADX WARNING: ").add(warn.getWarn()).add(" */"));
|
||||
warns.stream().distinct().sorted()
|
||||
.forEach(warn -> code.startLine("/* JADX WARNING: ").addMultiLine(warn).add(" */"));
|
||||
}
|
||||
}
|
||||
|
||||
private void addFields(CodeWriter code) throws CodegenException {
|
||||
addEnumFields(code);
|
||||
for (FieldNode f : cls.getFields()) {
|
||||
if (f.contains(AFlag.DONT_GENERATE)) {
|
||||
continue;
|
||||
}
|
||||
annotationGen.addForField(code, f);
|
||||
addField(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());
|
||||
FieldInitAttr fv = f.get(AType.FIELD_INIT);
|
||||
if (fv != null) {
|
||||
code.add(" = ");
|
||||
if (fv.getValue() == null) {
|
||||
code.add(TypeGen.literalToString(0, f.getType(), cls));
|
||||
} else {
|
||||
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());
|
||||
}
|
||||
public void addField(CodeWriter code, FieldNode f) {
|
||||
if (f.contains(AFlag.DONT_GENERATE)) {
|
||||
return;
|
||||
}
|
||||
CodeGenUtils.addComments(code, f);
|
||||
annotationGen.addForField(code, f);
|
||||
|
||||
if (f.getFieldInfo().isRenamed()) {
|
||||
code.newLine();
|
||||
CodeGenUtils.addRenamedComment(code, f, f.getName());
|
||||
}
|
||||
code.startLine(f.getAccessFlags().makeString());
|
||||
useType(code, f.getType());
|
||||
code.add(' ');
|
||||
code.attachDefinition(f);
|
||||
code.add(f.getAlias());
|
||||
FieldInitAttr fv = f.get(AType.FIELD_INIT);
|
||||
if (fv != null) {
|
||||
code.add(" = ");
|
||||
if (fv.getValue() == null) {
|
||||
code.add(TypeGen.literalToString(0, f.getType(), cls, fallback));
|
||||
} else {
|
||||
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.add(';');
|
||||
}
|
||||
|
||||
private boolean isFieldsPresents() {
|
||||
@@ -396,7 +395,7 @@ public class ClassGen {
|
||||
return;
|
||||
}
|
||||
InsnGen igen = null;
|
||||
for (Iterator<EnumField> it = enumFields.getFields().iterator(); it.hasNext(); ) {
|
||||
for (Iterator<EnumField> it = enumFields.getFields().iterator(); it.hasNext();) {
|
||||
EnumField f = it.next();
|
||||
code.startLine(f.getField().getAlias());
|
||||
ConstructorInsn constrInsn = f.getConstrInsn();
|
||||
@@ -458,7 +457,7 @@ public class ClassGen {
|
||||
}
|
||||
|
||||
public void useClass(CodeWriter code, ArgType type) {
|
||||
useClass(code, ClassInfo.extCls(cls.root(), type));
|
||||
useClass(code, ClassInfo.fromType(cls.root(), type));
|
||||
ArgType[] generics = type.getGenericTypes();
|
||||
if (generics != null) {
|
||||
code.add('<');
|
||||
@@ -487,18 +486,28 @@ public class ClassGen {
|
||||
public void useClass(CodeWriter code, ClassInfo classInfo) {
|
||||
ClassNode classNode = cls.dex().resolveClass(classInfo);
|
||||
if (classNode != null) {
|
||||
code.attachAnnotation(classNode);
|
||||
useClass(code, classNode);
|
||||
} else {
|
||||
addClsName(code, classInfo);
|
||||
}
|
||||
String baseClass = useClassInternal(cls.getAlias(), classInfo.getAlias());
|
||||
code.add(baseClass);
|
||||
}
|
||||
|
||||
public void useClass(CodeWriter code, ClassNode classNode) {
|
||||
code.attachAnnotation(classNode);
|
||||
addClsName(code, classNode.getClassInfo());
|
||||
}
|
||||
|
||||
private void addClsName(CodeWriter code, ClassInfo classInfo) {
|
||||
String clsName = useClassInternal(cls.getClassInfo(), classInfo);
|
||||
code.add(clsName);
|
||||
}
|
||||
|
||||
private String useClassInternal(ClassInfo useCls, ClassInfo extClsInfo) {
|
||||
String fullName = extClsInfo.getFullName();
|
||||
String fullName = extClsInfo.getAliasFullName();
|
||||
if (fallback || !useImports) {
|
||||
return fullName;
|
||||
}
|
||||
String shortName = extClsInfo.getShortName();
|
||||
String shortName = extClsInfo.getAliasShortName();
|
||||
if (extClsInfo.getPackage().equals("java.lang") && extClsInfo.getParentClass() == null) {
|
||||
return shortName;
|
||||
}
|
||||
@@ -524,15 +533,15 @@ public class ClassGen {
|
||||
if (extClsInfo.isDefaultPackage()) {
|
||||
return shortName;
|
||||
}
|
||||
if (extClsInfo.getPackage().equals(useCls.getPackage())) {
|
||||
fullName = extClsInfo.getNameWithoutPackage();
|
||||
if (extClsInfo.getAliasPkg().equals(useCls.getAliasPkg())) {
|
||||
fullName = extClsInfo.getAliasNameWithoutPackage();
|
||||
}
|
||||
for (ClassInfo importCls : getImports()) {
|
||||
if (!importCls.equals(extClsInfo)
|
||||
&& importCls.getShortName().equals(shortName)) {
|
||||
&& importCls.getAliasShortName().equals(shortName)) {
|
||||
if (extClsInfo.isInner()) {
|
||||
String parent = useClassInternal(useCls, extClsInfo.getParentClass().getAlias());
|
||||
return parent + "." + shortName;
|
||||
String parent = useClassInternal(useCls, extClsInfo.getParentClass());
|
||||
return parent + '.' + shortName;
|
||||
} else {
|
||||
return fullName;
|
||||
}
|
||||
@@ -544,13 +553,13 @@ public class ClassGen {
|
||||
|
||||
private void addImport(ClassInfo classInfo) {
|
||||
if (parentGen != null) {
|
||||
parentGen.addImport(classInfo.getAlias());
|
||||
parentGen.addImport(classInfo);
|
||||
} else {
|
||||
imports.add(classInfo);
|
||||
}
|
||||
}
|
||||
|
||||
private Set<ClassInfo> getImports() {
|
||||
public Set<ClassInfo> getImports() {
|
||||
if (parentGen != null) {
|
||||
return parentGen.getImports();
|
||||
} else {
|
||||
@@ -580,15 +589,15 @@ public class ClassGen {
|
||||
if (useCls == null) {
|
||||
return false;
|
||||
}
|
||||
String shortName = searchCls.getShortName();
|
||||
if (useCls.getShortName().equals(shortName)) {
|
||||
String shortName = searchCls.getAliasShortName();
|
||||
if (useCls.getAliasShortName().equals(shortName)) {
|
||||
return true;
|
||||
}
|
||||
ClassNode classNode = dex.resolveClass(useCls);
|
||||
if (classNode != null) {
|
||||
for (ClassNode inner : classNode.getInnerClasses()) {
|
||||
if (inner.getShortName().equals(shortName)
|
||||
&& !inner.getAlias().equals(searchCls)) {
|
||||
&& !inner.getFullName().equals(searchCls.getAliasFullName())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -596,17 +605,10 @@ public class ClassGen {
|
||||
return searchCollision(dex, useCls.getParentClass(), searchCls);
|
||||
}
|
||||
|
||||
private void insertSourceFileInfo(CodeWriter code, AttrNode node) {
|
||||
SourceFileAttr sourceFileAttr = node.get(AType.SOURCE_FILE);
|
||||
if (sourceFileAttr != null) {
|
||||
code.startLine("/* compiled from: ").add(sourceFileAttr.getFileName()).add(" */");
|
||||
}
|
||||
}
|
||||
|
||||
private void insertRenameInfo(CodeWriter code, ClassNode cls) {
|
||||
ClassInfo classInfo = cls.getClassInfo();
|
||||
if (classInfo.isRenamed()) {
|
||||
code.startLine("/* renamed from: ").add(classInfo.getType().getObject()).add(" */");
|
||||
if (classInfo.hasAlias()) {
|
||||
CodeGenUtils.addRenamedComment(code, cls, classInfo.getType().getObject());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,61 @@
|
||||
package jadx.core.codegen;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.core.codegen.json.JsonCodeGen;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.utils.exceptions.CodegenException;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
public class CodeGen {
|
||||
|
||||
public boolean visit(ClassNode cls) throws CodegenException {
|
||||
ClassGen clsGen = new ClassGen(cls, cls.root().getArgs());
|
||||
CodeWriter clsCode = clsGen.makeClass();
|
||||
clsCode.finish();
|
||||
cls.setCode(clsCode);
|
||||
return false;
|
||||
public static void generate(ClassNode cls) {
|
||||
if (cls.contains(AFlag.DONT_GENERATE)) {
|
||||
cls.setCode(CodeWriter.EMPTY);
|
||||
} else {
|
||||
JadxArgs args = cls.root().getArgs();
|
||||
switch (args.getOutputFormat()) {
|
||||
case JAVA:
|
||||
generateJavaCode(cls, args);
|
||||
break;
|
||||
|
||||
case JSON:
|
||||
generateJson(cls);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void generateJavaCode(ClassNode cls, JadxArgs args) {
|
||||
ClassGen clsGen = new ClassGen(cls, args);
|
||||
CodeWriter code = wrapCodeGen(cls, clsGen::makeClass);
|
||||
cls.setCode(code);
|
||||
}
|
||||
|
||||
private static void generateJson(ClassNode cls) {
|
||||
JsonCodeGen codeGen = new JsonCodeGen(cls);
|
||||
String clsJson = wrapCodeGen(cls, codeGen::process);
|
||||
cls.setCode(new CodeWriter(clsJson));
|
||||
}
|
||||
|
||||
private static <R> R wrapCodeGen(ClassNode cls, Callable<R> codeGenFunc) {
|
||||
try {
|
||||
return codeGenFunc.call();
|
||||
} catch (Exception e) {
|
||||
if (cls.contains(AFlag.RESTART_CODEGEN)) {
|
||||
cls.remove(AFlag.RESTART_CODEGEN);
|
||||
try {
|
||||
return codeGenFunc.call();
|
||||
} catch (Exception ex) {
|
||||
throw new JadxRuntimeException("Code generation error after restart", ex);
|
||||
}
|
||||
} else {
|
||||
throw new JadxRuntimeException("Code generation error", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private CodeGen() {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import java.io.File;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
@@ -24,6 +23,8 @@ public class CodeWriter {
|
||||
public static final String NL = System.getProperty("line.separator");
|
||||
public static final String INDENT_STR = " ";
|
||||
|
||||
public static final CodeWriter EMPTY = new CodeWriter().finish();
|
||||
|
||||
private static final boolean ADD_LINE_NUMBERS = false;
|
||||
|
||||
private static final String[] INDENT_CACHE = {
|
||||
@@ -35,7 +36,7 @@ public class CodeWriter {
|
||||
INDENT_STR + INDENT_STR + INDENT_STR + INDENT_STR + INDENT_STR,
|
||||
};
|
||||
|
||||
private StringBuilder buf = new StringBuilder();
|
||||
private StringBuilder buf;
|
||||
@Nullable
|
||||
private String code;
|
||||
private String indentStr;
|
||||
@@ -47,6 +48,7 @@ public class CodeWriter {
|
||||
private Map<Integer, Integer> lineMap = Collections.emptyMap();
|
||||
|
||||
public CodeWriter() {
|
||||
this.buf = new StringBuilder();
|
||||
this.indent = 0;
|
||||
this.indentStr = "";
|
||||
if (ADD_LINE_NUMBERS) {
|
||||
@@ -54,6 +56,12 @@ public class CodeWriter {
|
||||
}
|
||||
}
|
||||
|
||||
// create filled instance (just string wrapper)
|
||||
public CodeWriter(String code) {
|
||||
this.buf = null;
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public CodeWriter startLine() {
|
||||
addLine();
|
||||
addLineIndent();
|
||||
@@ -95,10 +103,12 @@ public class CodeWriter {
|
||||
}
|
||||
|
||||
public CodeWriter addMultiLine(String str) {
|
||||
buf.append(str);
|
||||
if (str.contains(NL)) {
|
||||
buf.append(str.replace(NL, NL + indentStr));
|
||||
line += StringUtils.countMatches(str, NL);
|
||||
offset = 0;
|
||||
} else {
|
||||
buf.append(str);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
@@ -221,6 +231,10 @@ public class CodeWriter {
|
||||
attachAnnotation(obj, new CodePosition(line, offset + 1));
|
||||
}
|
||||
|
||||
public void attachLineAnnotation(Object obj) {
|
||||
attachAnnotation(obj, new CodePosition(line, 0));
|
||||
}
|
||||
|
||||
private Object attachAnnotation(Object obj, CodePosition pos) {
|
||||
if (annotations.isEmpty()) {
|
||||
annotations = new HashMap<>();
|
||||
@@ -250,27 +264,27 @@ public class CodeWriter {
|
||||
return lineMap;
|
||||
}
|
||||
|
||||
public void finish() {
|
||||
public CodeWriter 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();
|
||||
annotations.entrySet().removeIf(entry -> {
|
||||
Object v = entry.getValue();
|
||||
if (v instanceof DefinitionWrapper) {
|
||||
LineAttrNode l = ((DefinitionWrapper) v).getNode();
|
||||
l.setDecompiledLine(entry.getKey().getLine());
|
||||
it.remove();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
private void removeFirstEmptyLine() {
|
||||
int len = NL.length();
|
||||
if (buf.substring(0, len).equals(NL)) {
|
||||
if (buf.length() > len && buf.substring(0, len).equals(NL)) {
|
||||
buf.delete(0, len);
|
||||
}
|
||||
}
|
||||
@@ -289,14 +303,14 @@ public class CodeWriter {
|
||||
}
|
||||
|
||||
public void save(File dir, String subDir, String fileName) {
|
||||
if(!ZipSecurity.isValidZipEntryName(subDir) || !ZipSecurity.isValidZipEntryName(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)) {
|
||||
if (!ZipSecurity.isValidZipEntryName(fileName)) {
|
||||
return;
|
||||
}
|
||||
save(new File(dir, fileName));
|
||||
|
||||
@@ -4,6 +4,7 @@ import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Queue;
|
||||
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.instructions.ArithNode;
|
||||
import jadx.core.dex.instructions.IfOp;
|
||||
import jadx.core.dex.instructions.InsnType;
|
||||
@@ -155,7 +156,7 @@ public class ConditionGen extends InsnGen {
|
||||
}
|
||||
|
||||
private boolean isWrapNeeded(IfCondition condition) {
|
||||
if (condition.isCompare()) {
|
||||
if (condition.isCompare() || condition.contains(AFlag.DONT_WRAP)) {
|
||||
return false;
|
||||
}
|
||||
return condition.getMode() != Mode.NOT;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package jadx.core.codegen;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
@@ -11,6 +10,8 @@ import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.core.Consts;
|
||||
import jadx.core.deobf.NameMapper;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
|
||||
@@ -34,12 +35,14 @@ import jadx.core.dex.instructions.InvokeType;
|
||||
import jadx.core.dex.instructions.NewArrayNode;
|
||||
import jadx.core.dex.instructions.SwitchNode;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.CodeVar;
|
||||
import jadx.core.dex.instructions.args.FieldArg;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.InsnWrapArg;
|
||||
import jadx.core.dex.instructions.args.LiteralArg;
|
||||
import jadx.core.dex.instructions.args.Named;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.instructions.args.SSAVar;
|
||||
import jadx.core.dex.instructions.mods.ConstructorInsn;
|
||||
import jadx.core.dex.instructions.mods.TernaryInsn;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
@@ -47,7 +50,6 @@ 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.exceptions.CodegenException;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
@@ -61,6 +63,7 @@ public class InsnGen {
|
||||
protected final MethodNode mth;
|
||||
protected final RootNode root;
|
||||
protected final boolean fallback;
|
||||
protected final boolean attachInsns;
|
||||
|
||||
protected enum Flags {
|
||||
BODY_ONLY,
|
||||
@@ -71,8 +74,9 @@ public class InsnGen {
|
||||
public InsnGen(MethodGen mgen, boolean fallback) {
|
||||
this.mgen = mgen;
|
||||
this.mth = mgen.getMethodNode();
|
||||
this.root = mth.dex().root();
|
||||
this.root = mth.root();
|
||||
this.fallback = fallback;
|
||||
this.attachInsns = root.getArgs().isJsonOutput();
|
||||
}
|
||||
|
||||
private boolean isFallback() {
|
||||
@@ -123,16 +127,20 @@ public class InsnGen {
|
||||
}
|
||||
|
||||
public void declareVar(CodeWriter code, RegisterArg arg) {
|
||||
if (arg.getSVar().contains(AFlag.FINAL)) {
|
||||
declareVar(code, arg.getSVar().getCodeVar());
|
||||
}
|
||||
|
||||
public void declareVar(CodeWriter code, CodeVar codeVar) {
|
||||
if (codeVar.isFinal()) {
|
||||
code.add("final ");
|
||||
}
|
||||
useType(code, arg.getType());
|
||||
useType(code, codeVar.getType());
|
||||
code.add(' ');
|
||||
code.add(mgen.getNameGen().assignArg(arg));
|
||||
code.add(mgen.getNameGen().assignArg(codeVar));
|
||||
}
|
||||
|
||||
private String lit(LiteralArg arg) {
|
||||
return TypeGen.literalToString(arg.getLiteral(), arg.getType(), mth);
|
||||
return TypeGen.literalToString(arg.getLiteral(), arg.getType(), mth, fallback);
|
||||
}
|
||||
|
||||
private void instanceField(CodeWriter code, FieldInfo field, InsnArg arg) throws CodegenException {
|
||||
@@ -201,33 +209,41 @@ public class InsnGen {
|
||||
mgen.getClassGen().useType(code, type);
|
||||
}
|
||||
|
||||
public boolean makeInsn(InsnNode insn, CodeWriter code) throws CodegenException {
|
||||
return makeInsn(insn, code, null);
|
||||
public void makeInsn(InsnNode insn, CodeWriter code) throws CodegenException {
|
||||
makeInsn(insn, code, null);
|
||||
}
|
||||
|
||||
protected boolean makeInsn(InsnNode insn, CodeWriter code, Flags flag) throws CodegenException {
|
||||
private static final Set<Flags> EMPTY_FLAGS = EnumSet.noneOf(Flags.class);
|
||||
private static final Set<Flags> BODY_ONLY_FLAG = EnumSet.of(Flags.BODY_ONLY);
|
||||
private static final Set<Flags> BODY_ONLY_NOWRAP_FLAGS = EnumSet.of(Flags.BODY_ONLY_NOWRAP);
|
||||
|
||||
protected void makeInsn(InsnNode insn, CodeWriter code, Flags flag) throws CodegenException {
|
||||
try {
|
||||
Set<Flags> state = EnumSet.noneOf(Flags.class);
|
||||
if (flag == Flags.BODY_ONLY || flag == Flags.BODY_ONLY_NOWRAP) {
|
||||
state.add(flag);
|
||||
makeInsnBody(code, insn, state);
|
||||
makeInsnBody(code, insn, flag == Flags.BODY_ONLY ? BODY_ONLY_FLAG : BODY_ONLY_NOWRAP_FLAGS);
|
||||
} else {
|
||||
if (flag != Flags.INLINE) {
|
||||
code.startLineWithNum(insn.getSourceLine());
|
||||
if (attachInsns) {
|
||||
code.attachLineAnnotation(insn);
|
||||
}
|
||||
}
|
||||
if (insn.getResult() != null && !insn.contains(AFlag.ARITH_ONEARG)) {
|
||||
assignVar(code, insn);
|
||||
code.add(" = ");
|
||||
if (insn.getResult() != null) {
|
||||
SSAVar var = insn.getResult().getSVar();
|
||||
if ((var == null || var.getUseCount() != 0 || insn.getType() != InsnType.CONSTRUCTOR)
|
||||
&& !insn.contains(AFlag.ARITH_ONEARG)) {
|
||||
assignVar(code, insn);
|
||||
code.add(" = ");
|
||||
}
|
||||
}
|
||||
makeInsnBody(code, insn, state);
|
||||
makeInsnBody(code, insn, EMPTY_FLAGS);
|
||||
if (flag != Flags.INLINE) {
|
||||
code.add(';');
|
||||
}
|
||||
}
|
||||
} catch (Throwable th) {
|
||||
throw new CodegenException(mth, "Error generate insn: " + insn, th);
|
||||
} catch (Exception e) {
|
||||
throw new CodegenException(mth, "Error generate insn: " + insn, e);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void makeInsnBody(CodeWriter code, InsnNode insn, Set<Flags> state) throws CodegenException {
|
||||
@@ -277,7 +293,8 @@ public class InsnGen {
|
||||
break;
|
||||
|
||||
case NOT:
|
||||
oneArgInsn(code, insn, state, '~');
|
||||
char op = insn.getArg(0).getType() == ArgType.BOOLEAN ? '!' : '~';
|
||||
oneArgInsn(code, insn, state, op);
|
||||
break;
|
||||
|
||||
case RETURN:
|
||||
@@ -363,6 +380,17 @@ public class InsnGen {
|
||||
filledNewArray((FilledNewArrayNode) insn, code);
|
||||
break;
|
||||
|
||||
case FILL_ARRAY:
|
||||
FillArrayNode arrayNode = (FillArrayNode) insn;
|
||||
if (fallback) {
|
||||
String arrStr = arrayNode.dataToString();
|
||||
addArg(code, insn.getArg(0));
|
||||
code.add(" = {").add(arrStr.substring(1, arrStr.length() - 1)).add("} // fill-array");
|
||||
} else {
|
||||
fillArray(code, arrayNode);
|
||||
}
|
||||
break;
|
||||
|
||||
case AGET:
|
||||
addArg(code, insn.getArg(0));
|
||||
code.add('[');
|
||||
@@ -406,7 +434,7 @@ public class InsnGen {
|
||||
if (wrap) {
|
||||
code.add('(');
|
||||
}
|
||||
for (Iterator<InsnArg> it = insn.getArguments().iterator(); it.hasNext(); ) {
|
||||
for (Iterator<InsnArg> it = insn.getArguments().iterator(); it.hasNext();) {
|
||||
addArg(code, it.next());
|
||||
if (it.hasNext()) {
|
||||
code.add(" + ");
|
||||
@@ -481,40 +509,20 @@ public class InsnGen {
|
||||
code.startLine('}');
|
||||
break;
|
||||
|
||||
case FILL_ARRAY:
|
||||
fallbackOnlyInsn(insn);
|
||||
FillArrayNode arrayNode = (FillArrayNode) insn;
|
||||
Object data = arrayNode.getData();
|
||||
String arrStr;
|
||||
if (data instanceof int[]) {
|
||||
arrStr = Arrays.toString((int[]) data);
|
||||
} else if (data instanceof short[]) {
|
||||
arrStr = Arrays.toString((short[]) data);
|
||||
} else if (data instanceof byte[]) {
|
||||
arrStr = Arrays.toString((byte[]) data);
|
||||
} else if (data instanceof long[]) {
|
||||
arrStr = Arrays.toString((long[]) data);
|
||||
} else {
|
||||
arrStr = "?";
|
||||
}
|
||||
code.add('{').add(arrStr.substring(1, arrStr.length() - 1)).add('}');
|
||||
break;
|
||||
|
||||
case NEW_INSTANCE:
|
||||
// only fallback - make new instance in constructor invoke
|
||||
fallbackOnlyInsn(insn);
|
||||
code.add("new ").add(insn.getResult().getType().toString());
|
||||
code.add("new ").add(insn.getResult().getInitType().toString());
|
||||
break;
|
||||
|
||||
case PHI:
|
||||
case MERGE:
|
||||
fallbackOnlyInsn(insn);
|
||||
code.add(insn.getType().toString()).add("(");
|
||||
code.add(insn.getType().toString()).add('(');
|
||||
for (InsnArg insnArg : insn.getArguments()) {
|
||||
addArg(code, insnArg);
|
||||
code.add(' ');
|
||||
}
|
||||
code.add(")");
|
||||
code.add(')');
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -522,6 +530,26 @@ public class InsnGen {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* In most cases must be combined with new array instructions.
|
||||
* Use one by one array fill (can be replaced with System.arrayCopy)
|
||||
*/
|
||||
private void fillArray(CodeWriter code, FillArrayNode arrayNode) throws CodegenException {
|
||||
code.add("// fill-array-data instruction");
|
||||
code.startLine();
|
||||
List<LiteralArg> args = arrayNode.getLiteralArgs(arrayNode.getElementType());
|
||||
InsnArg arrArg = arrayNode.getArg(0);
|
||||
int len = args.size();
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (i != 0) {
|
||||
code.add(';');
|
||||
code.startLine();
|
||||
}
|
||||
addArg(code, arrArg);
|
||||
code.add('[').add(Integer.toString(i)).add("] = ").add(lit(args.get(i)));
|
||||
}
|
||||
}
|
||||
|
||||
private void oneArgInsn(CodeWriter code, InsnNode insn, Set<Flags> state, char op) throws CodegenException {
|
||||
boolean wrap = state.contains(Flags.BODY_ONLY);
|
||||
if (wrap) {
|
||||
@@ -536,13 +564,19 @@ public class InsnGen {
|
||||
|
||||
private void fallbackOnlyInsn(InsnNode insn) throws CodegenException {
|
||||
if (!fallback) {
|
||||
throw new CodegenException(insn.getType() + " can be used only in fallback mode");
|
||||
String msg = insn.getType() + " instruction can be used only in fallback mode";
|
||||
CodegenException e = new CodegenException(msg);
|
||||
mth.addError(msg, e);
|
||||
mth.getParentClass().getTopParentClass().add(AFlag.RESTART_CODEGEN);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private void filledNewArray(FilledNewArrayNode insn, CodeWriter code) throws CodegenException {
|
||||
code.add("new ");
|
||||
useType(code, insn.getArrayType());
|
||||
if (!insn.contains(AFlag.DECLARE_VAR)) {
|
||||
code.add("new ");
|
||||
useType(code, insn.getArrayType());
|
||||
}
|
||||
code.add('{');
|
||||
int c = insn.getArgsCount();
|
||||
for (int i = 0; i < c; i++) {
|
||||
@@ -557,8 +591,8 @@ public class InsnGen {
|
||||
private void makeConstructor(ConstructorInsn insn, CodeWriter code)
|
||||
throws CodegenException {
|
||||
ClassNode cls = mth.dex().resolveClass(insn.getClassType());
|
||||
if (cls != null && cls.contains(AFlag.ANONYMOUS_CLASS) && !fallback) {
|
||||
inlineAnonymousConstr(code, cls, insn);
|
||||
if (cls != null && cls.isAnonymous() && !fallback) {
|
||||
inlineAnonymousConstructor(code, cls, insn);
|
||||
return;
|
||||
}
|
||||
if (insn.isSelf()) {
|
||||
@@ -571,33 +605,50 @@ public class InsnGen {
|
||||
} else {
|
||||
code.add("new ");
|
||||
useClass(code, insn.getClassType());
|
||||
ArgType argType = insn.getResult().getSVar().getCodeVar().getType();
|
||||
if (argType.isGeneric()) {
|
||||
code.add('<');
|
||||
if (insn.contains(AFlag.EXPLICIT_GENERICS)) {
|
||||
boolean first = true;
|
||||
for (ArgType type : argType.getGenericTypes()) {
|
||||
if (!first) {
|
||||
code.add(',');
|
||||
}
|
||||
mgen.getClassGen().useType(code, type);
|
||||
first = false;
|
||||
}
|
||||
}
|
||||
code.add('>');
|
||||
}
|
||||
}
|
||||
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;
|
||||
private void inlineAnonymousConstructor(CodeWriter code, ClassNode cls, ConstructorInsn insn) throws CodegenException {
|
||||
if (this.mth.getParentClass() == cls) {
|
||||
cls.remove(AFlag.ANONYMOUS_CLASS);
|
||||
cls.remove(AFlag.DONT_GENERATE);
|
||||
mth.getParentClass().getTopParentClass().add(AFlag.RESTART_CODEGEN);
|
||||
throw new CodegenException("Anonymous inner class unlimited recursion detected."
|
||||
+ " Convert class to inner: " + cls.getClassInfo().getFullName());
|
||||
}
|
||||
|
||||
cls.add(AFlag.DONT_GENERATE);
|
||||
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);
|
||||
// hide empty anonymous constructors
|
||||
for (MethodNode ctor : cls.getMethods()) {
|
||||
if (ctor.contains(AFlag.ANONYMOUS_CONSTRUCTOR)
|
||||
&& RegionUtils.isEmpty(ctor.getRegion())) {
|
||||
ctor.add(AFlag.DONT_GENERATE);
|
||||
}
|
||||
}
|
||||
|
||||
code.add("new ");
|
||||
if (parent == null) {
|
||||
code.add("Object");
|
||||
@@ -615,11 +666,8 @@ public class InsnGen {
|
||||
|
||||
// inline method
|
||||
MethodNode callMthNode = mth.root().deepResolveMethod(callMth);
|
||||
if (callMthNode != null) {
|
||||
if (inlineMethod(callMthNode, insn, code)) {
|
||||
return;
|
||||
}
|
||||
callMth = callMthNode.getMethodInfo();
|
||||
if (callMthNode != null && inlineMethod(callMthNode, insn, code)) {
|
||||
return;
|
||||
}
|
||||
|
||||
int k = 0;
|
||||
@@ -637,13 +685,18 @@ public class InsnGen {
|
||||
break;
|
||||
|
||||
case SUPER:
|
||||
ClassInfo superCallCls = getClassForSuperCall(code, callMth);
|
||||
if (superCallCls != null) {
|
||||
useClass(code, superCallCls);
|
||||
code.add('.');
|
||||
}
|
||||
// use 'super' instead 'this' in 0 arg
|
||||
code.add("super").add('.');
|
||||
k++;
|
||||
break;
|
||||
|
||||
case STATIC:
|
||||
ClassInfo insnCls = mth.getParentClass().getAlias();
|
||||
ClassInfo insnCls = mth.getParentClass().getClassInfo();
|
||||
ClassInfo declClass = callMth.getDeclClass();
|
||||
if (!insnCls.equals(declClass)) {
|
||||
useClass(code, declClass);
|
||||
@@ -653,11 +706,43 @@ public class InsnGen {
|
||||
}
|
||||
if (callMthNode != null) {
|
||||
code.attachAnnotation(callMthNode);
|
||||
code.add(callMthNode.getAlias());
|
||||
} else {
|
||||
code.add(callMth.getAlias());
|
||||
}
|
||||
code.add(callMth.getAlias());
|
||||
generateMethodArguments(code, insn, k, callMthNode);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private ClassInfo getClassForSuperCall(CodeWriter code, MethodInfo callMth) {
|
||||
ClassNode useCls = mth.getParentClass();
|
||||
ClassInfo insnCls = useCls.getClassInfo();
|
||||
ClassInfo declClass = callMth.getDeclClass();
|
||||
if (insnCls.equals(declClass)) {
|
||||
return null;
|
||||
}
|
||||
ClassNode topClass = useCls.getTopParentClass();
|
||||
if (topClass.getClassInfo().equals(declClass)) {
|
||||
return declClass;
|
||||
}
|
||||
// search call class
|
||||
ClassNode nextParent = useCls;
|
||||
do {
|
||||
ClassInfo nextClsInfo = nextParent.getClassInfo();
|
||||
if (nextClsInfo.equals(declClass)
|
||||
|| ArgType.isInstanceOf(mth.root(), nextClsInfo.getType(), declClass.getType())) {
|
||||
if (nextParent == useCls) {
|
||||
return null;
|
||||
}
|
||||
return nextClsInfo;
|
||||
}
|
||||
nextParent = nextParent.getParentClass();
|
||||
} while (nextParent != null && nextParent != topClass);
|
||||
|
||||
// search failed, just return parent class
|
||||
return useCls.getParentClass().getClassInfo();
|
||||
}
|
||||
|
||||
void generateMethodArguments(CodeWriter code, InsnNode insn, int startArgNum,
|
||||
@Nullable MethodNode callMth) throws CodegenException {
|
||||
int k = startArgNum;
|
||||
@@ -707,14 +792,43 @@ public class InsnGen {
|
||||
* Add additional cast for overloaded method argument.
|
||||
*/
|
||||
private boolean processOverloadedArg(CodeWriter code, MethodNode callMth, InsnArg arg, int origPos) {
|
||||
ArgType origType = callMth.getMethodInfo().getArgumentsTypes().get(origPos);
|
||||
if (!arg.getType().equals(origType)) {
|
||||
code.add('(');
|
||||
useType(code, origType);
|
||||
code.add(") ");
|
||||
return true;
|
||||
ArgType origType;
|
||||
List<RegisterArg> arguments = callMth.getArguments(false);
|
||||
if (arguments == null || arguments.isEmpty()) {
|
||||
mth.addComment("JADX INFO: used method not loaded: " + callMth + ", types can be incorrect");
|
||||
origType = callMth.getMethodInfo().getArgumentsTypes().get(origPos);
|
||||
} else {
|
||||
origType = arguments.get(origPos).getInitType();
|
||||
if (origType.isGenericType() && !callMth.getParentClass().equals(mth.getParentClass())) {
|
||||
// cancel cast
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
ArgType argType = arg.getType();
|
||||
if (argType.equals(origType)
|
||||
// null cast to object
|
||||
&& (!arg.isLiteral() || ((LiteralArg) arg).getLiteral() != 0
|
||||
|| (!argType.isArray() && !argType.isObject()))) {
|
||||
return false;
|
||||
}
|
||||
if (origType.isGeneric()) {
|
||||
if (argType.isObject()) {
|
||||
if (!argType.isGeneric() && arg.isInsnWrap()) {
|
||||
((InsnWrapArg) arg).getWrapInsn().getResult().setType(
|
||||
ArgType.generic(argType.getObject(), origType.getGenericTypes()));
|
||||
}
|
||||
if (origType.getObject().equals(argType.getObject())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (arg.isInsnWrap()) {
|
||||
((InsnWrapArg) arg).getWrapInsn().add(AFlag.EXPLICIT_GENERICS);
|
||||
}
|
||||
}
|
||||
code.add('(');
|
||||
useType(code, origType);
|
||||
code.add(") ");
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -748,6 +862,9 @@ public class InsnGen {
|
||||
return false;
|
||||
}
|
||||
InsnNode inl = mia.getInsn();
|
||||
if (Consts.DEBUG) {
|
||||
code.add("/* inline method: ").add(callMthNode.toString()).add("*/").startLine();
|
||||
}
|
||||
if (callMthNode.getMethodInfo().getArgumentsTypes().isEmpty()) {
|
||||
makeInsn(inl, code, Flags.BODY_ONLY);
|
||||
} else {
|
||||
@@ -794,6 +911,7 @@ public class InsnGen {
|
||||
} else {
|
||||
condGen.wrap(code, insn.getCondition());
|
||||
code.add(" ? ");
|
||||
addCastIfNeeded(code, first, second);
|
||||
addArg(code, first, false);
|
||||
code.add(" : ");
|
||||
addArg(code, second, false);
|
||||
@@ -803,6 +921,33 @@ public class InsnGen {
|
||||
}
|
||||
}
|
||||
|
||||
private void addCastIfNeeded(CodeWriter code, InsnArg first, InsnArg second) {
|
||||
if (first.isLiteral() && second.isLiteral()) {
|
||||
if (first.getType() == ArgType.BYTE) {
|
||||
long lit1 = ((LiteralArg) first).getLiteral();
|
||||
long lit2 = ((LiteralArg) second).getLiteral();
|
||||
if (lit1 != Byte.MAX_VALUE && lit1 != Byte.MIN_VALUE
|
||||
&& lit2 != Byte.MAX_VALUE && lit2 != Byte.MIN_VALUE) {
|
||||
code.add("(byte) ");
|
||||
}
|
||||
} else if (first.getType() == ArgType.SHORT) {
|
||||
long lit1 = ((LiteralArg) first).getLiteral();
|
||||
long lit2 = ((LiteralArg) second).getLiteral();
|
||||
if (lit1 != Short.MAX_VALUE && lit1 != Short.MIN_VALUE
|
||||
&& lit2 != Short.MAX_VALUE && lit2 != Short.MIN_VALUE) {
|
||||
code.add("(short) ");
|
||||
}
|
||||
} else if (first.getType() == ArgType.CHAR) {
|
||||
long lit1 = ((LiteralArg) first).getLiteral();
|
||||
long lit2 = ((LiteralArg) second).getLiteral();
|
||||
if (!NameMapper.isPrintableChar((char) (lit1))
|
||||
&& !NameMapper.isPrintableChar((char) (lit2))) {
|
||||
code.add("(char) ");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void makeArith(ArithNode insn, CodeWriter code, Set<Flags> state) throws CodegenException {
|
||||
if (insn.contains(AFlag.ARITH_ONEARG)) {
|
||||
makeArithOneArg(insn, code);
|
||||
|
||||
@@ -3,16 +3,21 @@ 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 com.android.dx.rop.code.AccessFlags;
|
||||
|
||||
import jadx.core.Consts;
|
||||
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.JumpInfo;
|
||||
import jadx.core.dex.info.AccessInfo;
|
||||
import jadx.core.dex.instructions.IfNode;
|
||||
import jadx.core.dex.instructions.InsnType;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.CodeVar;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.instructions.args.SSAVar;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
@@ -20,10 +25,12 @@ import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.trycatch.CatchAttr;
|
||||
import jadx.core.dex.visitors.DepthTraversal;
|
||||
import jadx.core.dex.visitors.FallbackModeVisitor;
|
||||
import jadx.core.utils.ErrorsCounter;
|
||||
import jadx.core.utils.CodeGenUtils;
|
||||
import jadx.core.utils.InsnUtils;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.CodegenException;
|
||||
import jadx.core.utils.exceptions.DecodeException;
|
||||
import jadx.core.utils.exceptions.JadxOverflowException;
|
||||
|
||||
public class MethodGen {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MethodGen.class);
|
||||
@@ -78,16 +85,24 @@ public class MethodGen {
|
||||
ai = ai.remove(AccessFlags.ACC_PUBLIC);
|
||||
}
|
||||
|
||||
if (mth.getMethodInfo().isRenamed()) {
|
||||
code.startLine("/* renamed from: ").add(mth.getName()).add(" */");
|
||||
if (mth.getMethodInfo().hasAlias() && !ai.isConstructor()) {
|
||||
CodeGenUtils.addRenamedComment(code, mth, mth.getName());
|
||||
}
|
||||
CodeGenUtils.addSourceFileInfo(code, mth);
|
||||
if (mth.contains(AFlag.INCONSISTENT_CODE)) {
|
||||
code.startLine("/* Code decompiled incorrectly, please refer to instructions dump. */");
|
||||
}
|
||||
|
||||
code.startLineWithNum(mth.getSourceLine());
|
||||
code.add(ai.makeString());
|
||||
if (Consts.DEBUG) {
|
||||
code.add(mth.isVirtual() ? "/* virtual */ " : "/* direct */ ");
|
||||
}
|
||||
|
||||
if (classGen.addGenericMap(code, mth.getGenericMap())) {
|
||||
if (classGen.addGenericMap(code, mth.getGenericMap(), false)) {
|
||||
code.add(' ');
|
||||
}
|
||||
if (mth.getAccessFlags().isConstructor()) {
|
||||
if (ai.isConstructor()) {
|
||||
code.attachDefinition(mth);
|
||||
code.add(classGen.getClassNode().getShortName()); // constructor
|
||||
} else {
|
||||
@@ -106,78 +121,128 @@ public class MethodGen {
|
||||
} else if (args.size() > 2) {
|
||||
args = args.subList(2, args.size());
|
||||
} else {
|
||||
LOG.warn(ErrorsCounter.formatMsg(mth,
|
||||
"Incorrect number of args for enum constructor: " + args.size()
|
||||
+ " (expected >= 2)"
|
||||
));
|
||||
mth.addComment("JADX WARN: Incorrect number of args for enum constructor: " + args.size() + " (expected >= 2)");
|
||||
}
|
||||
} else if (mth.contains(AFlag.SKIP_FIRST_ARG)) {
|
||||
args = args.subList(1, args.size());
|
||||
}
|
||||
addMethodArguments(code, args);
|
||||
code.add(')');
|
||||
|
||||
annotationGen.addThrows(mth, code);
|
||||
|
||||
// add default value if in annotation class
|
||||
if (mth.getParentClass().getAccessFlags().isAnnotation()) {
|
||||
Object def = annotationGen.getAnnotationDefaultValue(mth.getName());
|
||||
if (def != null) {
|
||||
code.add(" default ");
|
||||
annotationGen.encodeValue(code, def);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void addMethodArguments(CodeWriter argsCode, List<RegisterArg> args) {
|
||||
private void addMethodArguments(CodeWriter code, List<RegisterArg> args) {
|
||||
MethodParameters paramsAnnotation = mth.get(AType.ANNOTATION_MTH_PARAMETERS);
|
||||
int i = 0;
|
||||
for (Iterator<RegisterArg> it = args.iterator(); it.hasNext(); ) {
|
||||
RegisterArg arg = it.next();
|
||||
Iterator<RegisterArg> it = args.iterator();
|
||||
while (it.hasNext()) {
|
||||
RegisterArg mthArg = it.next();
|
||||
SSAVar ssaVar = mthArg.getSVar();
|
||||
CodeVar var;
|
||||
if (ssaVar == null) {
|
||||
// null for abstract or interface methods
|
||||
var = CodeVar.fromMthArg(mthArg);
|
||||
} else {
|
||||
var = ssaVar.getCodeVar();
|
||||
}
|
||||
|
||||
// add argument annotation
|
||||
if (paramsAnnotation != null) {
|
||||
annotationGen.addForParameter(argsCode, paramsAnnotation, i);
|
||||
annotationGen.addForParameter(code, paramsAnnotation, i);
|
||||
}
|
||||
SSAVar argSVar = arg.getSVar();
|
||||
if (argSVar != null && argSVar.contains(AFlag.FINAL)) {
|
||||
argsCode.add("final ");
|
||||
if (var.isFinal()) {
|
||||
code.add("final ");
|
||||
}
|
||||
ArgType argType;
|
||||
if (var.getType() == ArgType.UNKNOWN) {
|
||||
// occur on decompilation errors
|
||||
argType = mthArg.getInitType();
|
||||
} else {
|
||||
argType = var.getType();
|
||||
}
|
||||
if (!it.hasNext() && mth.getAccessFlags().isVarArgs()) {
|
||||
// change last array argument to varargs
|
||||
ArgType type = arg.getType();
|
||||
if (type.isArray()) {
|
||||
ArgType elType = type.getArrayElement();
|
||||
classGen.useType(argsCode, elType);
|
||||
argsCode.add("...");
|
||||
if (argType.isArray()) {
|
||||
ArgType elType = argType.getArrayElement();
|
||||
classGen.useType(code, elType);
|
||||
code.add("...");
|
||||
} else {
|
||||
LOG.warn(ErrorsCounter.formatMsg(mth, "Last argument in varargs method not array"));
|
||||
classGen.useType(argsCode, arg.getType());
|
||||
mth.addComment("JADX INFO: Last argument in varargs method is not array: " + var);
|
||||
classGen.useType(code, argType);
|
||||
}
|
||||
} else {
|
||||
classGen.useType(argsCode, arg.getType());
|
||||
classGen.useType(code, argType);
|
||||
}
|
||||
argsCode.add(' ');
|
||||
argsCode.add(nameGen.assignArg(arg));
|
||||
code.add(' ');
|
||||
code.add(nameGen.assignArg(var));
|
||||
|
||||
i++;
|
||||
if (it.hasNext()) {
|
||||
argsCode.add(", ");
|
||||
code.add(", ");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void addInstructions(CodeWriter code) throws CodegenException {
|
||||
if (mth.contains(AType.JADX_ERROR)
|
||||
|| mth.contains(AFlag.INCONSISTENT_CODE)
|
||||
|| mth.getRegion() == null) {
|
||||
code.startLine("/*");
|
||||
if (mth.root().getArgs().isFallbackMode()) {
|
||||
addFallbackMethodCode(code);
|
||||
code.startLine("*/");
|
||||
|
||||
code.startLine("throw new UnsupportedOperationException(\"Method not decompiled: ")
|
||||
.add(mth.toString())
|
||||
.add("\");");
|
||||
} else if (classGen.isFallbackMode()) {
|
||||
dumpInstructions(code);
|
||||
} else {
|
||||
addRegionInsns(code);
|
||||
}
|
||||
}
|
||||
|
||||
public void addRegionInsns(CodeWriter code) throws CodegenException {
|
||||
try {
|
||||
RegionGen regionGen = new RegionGen(this);
|
||||
regionGen.makeRegion(code, mth.getRegion());
|
||||
} catch (StackOverflowError | BootstrapMethodError e) {
|
||||
mth.addError("Method code generation error", new JadxOverflowException("StackOverflow"));
|
||||
classGen.insertDecompilationProblems(code, mth);
|
||||
dumpInstructions(code);
|
||||
} catch (Exception e) {
|
||||
if (mth.getParentClass().getTopParentClass().contains(AFlag.RESTART_CODEGEN)) {
|
||||
throw e;
|
||||
}
|
||||
mth.addError("Method code generation error", e);
|
||||
classGen.insertDecompilationProblems(code, mth);
|
||||
dumpInstructions(code);
|
||||
}
|
||||
}
|
||||
|
||||
public void dumpInstructions(CodeWriter code) {
|
||||
code.startLine("/*");
|
||||
addFallbackMethodCode(code);
|
||||
code.startLine("*/");
|
||||
|
||||
code.startLine("throw new UnsupportedOperationException(\"Method not decompiled: ")
|
||||
.add(mth.getParentClass().getClassInfo().getAliasFullName())
|
||||
.add('.')
|
||||
.add(mth.getAlias())
|
||||
.add('(')
|
||||
.add(Utils.listToString(mth.getMethodInfo().getArgumentsTypes()))
|
||||
.add("):")
|
||||
.add(mth.getMethodInfo().getReturnType().toString())
|
||||
.add("\");");
|
||||
}
|
||||
|
||||
public void addFallbackMethodCode(CodeWriter code) {
|
||||
if (mth.getInstructions() == null) {
|
||||
// load original instructions
|
||||
try {
|
||||
mth.unload();
|
||||
mth.load();
|
||||
DepthTraversal.visit(new FallbackModeVisitor(), mth);
|
||||
} catch (DecodeException e) {
|
||||
@@ -191,42 +256,81 @@ public class MethodGen {
|
||||
code.startLine("// Can't load method instructions.");
|
||||
return;
|
||||
}
|
||||
code.incIndent();
|
||||
if (mth.getThisArg() != null) {
|
||||
code.startLine(nameGen.useArg(mth.getThisArg())).add(" = this;");
|
||||
}
|
||||
addFallbackInsns(code, mth, insnArr, true);
|
||||
code.decIndent();
|
||||
}
|
||||
|
||||
public static void addFallbackInsns(CodeWriter code, MethodNode mth, InsnNode[] insnArr, boolean addLabels) {
|
||||
InsnGen insnGen = new InsnGen(getFallbackMethodGen(mth), true);
|
||||
boolean attachInsns = mth.root().getArgs().isJsonOutput();
|
||||
InsnNode prevInsn = null;
|
||||
for (InsnNode insn : insnArr) {
|
||||
if (insn == null || insn.getType() == InsnType.NOP) {
|
||||
if (insn == null) {
|
||||
continue;
|
||||
}
|
||||
if (addLabels && (insn.contains(AType.JUMP) || insn.contains(AType.EXC_HANDLER))) {
|
||||
if (addLabels && needLabel(insn, prevInsn)) {
|
||||
code.decIndent();
|
||||
code.startLine(getLabelName(insn.getOffset()) + ":");
|
||||
code.startLine(getLabelName(insn.getOffset()) + ':');
|
||||
code.incIndent();
|
||||
}
|
||||
if (insn.getType() == InsnType.NOP) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
if (insnGen.makeInsn(insn, code)) {
|
||||
CatchAttr catchAttr = insn.get(AType.CATCH_BLOCK);
|
||||
if (catchAttr != null) {
|
||||
code.add("\t " + catchAttr);
|
||||
code.startLine();
|
||||
if (attachInsns) {
|
||||
code.attachLineAnnotation(insn);
|
||||
}
|
||||
RegisterArg resArg = insn.getResult();
|
||||
if (resArg != null) {
|
||||
ArgType varType = resArg.getInitType();
|
||||
if (varType.isTypeKnown()) {
|
||||
code.add(varType.toString()).add(' ');
|
||||
}
|
||||
}
|
||||
insnGen.makeInsn(insn, code, InsnGen.Flags.INLINE);
|
||||
CatchAttr catchAttr = insn.get(AType.CATCH_BLOCK);
|
||||
if (catchAttr != null) {
|
||||
code.add(" // " + catchAttr);
|
||||
}
|
||||
} catch (CodegenException e) {
|
||||
LOG.debug("Error generate fallback instruction: ", e.getCause());
|
||||
code.startLine("// error: " + insn);
|
||||
}
|
||||
prevInsn = insn;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean needLabel(InsnNode insn, InsnNode prevInsn) {
|
||||
if (insn.contains(AType.EXC_HANDLER)) {
|
||||
return true;
|
||||
}
|
||||
if (insn.contains(AType.JUMP)) {
|
||||
// don't add label for ifs else branch
|
||||
if (prevInsn != null && prevInsn.getType() == InsnType.IF) {
|
||||
List<JumpInfo> jumps = insn.getAll(AType.JUMP);
|
||||
if (jumps.size() == 1) {
|
||||
JumpInfo jump = jumps.get(0);
|
||||
if (jump.getSrc() == prevInsn.getOffset() && jump.getDest() == insn.getOffset()) {
|
||||
int target = ((IfNode) prevInsn).getTarget();
|
||||
return insn.getOffset() == target;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return fallback variant of method codegen
|
||||
*/
|
||||
public static MethodGen getFallbackMethodGen(MethodNode mth) {
|
||||
ClassGen clsGen = new ClassGen(mth.getParentClass(), null, true, true, true);
|
||||
ClassGen clsGen = new ClassGen(mth.getParentClass(), null, false, true, true);
|
||||
return new MethodGen(clsGen, mth);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,65 +1,83 @@
|
||||
package jadx.core.codegen;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
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;
|
||||
import jadx.core.dex.instructions.InvokeNode;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.CodeVar;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.InsnWrapArg;
|
||||
import jadx.core.dex.instructions.args.NamedArg;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.instructions.args.SSAVar;
|
||||
import jadx.core.dex.instructions.mods.ConstructorInsn;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.utils.StringUtils;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
public class NameGen {
|
||||
|
||||
private static final Map<String, String> OBJ_ALIAS;
|
||||
|
||||
private final Set<String> varNames = new LinkedHashSet<>();
|
||||
private final Set<String> varNames = new HashSet<>();
|
||||
private final MethodNode mth;
|
||||
private final boolean fallback;
|
||||
|
||||
static {
|
||||
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");
|
||||
OBJ_ALIAS.put(Consts.CLASS_OBJECT, "obj");
|
||||
OBJ_ALIAS.put("java.util.Iterator", "it");
|
||||
OBJ_ALIAS.put("java.lang.Boolean", "bool");
|
||||
OBJ_ALIAS.put("java.lang.Short", "sh");
|
||||
OBJ_ALIAS.put("java.lang.Integer", "num");
|
||||
OBJ_ALIAS.put("java.lang.Character", "ch");
|
||||
OBJ_ALIAS.put("java.lang.Byte", "b");
|
||||
OBJ_ALIAS.put("java.lang.Float", "f");
|
||||
OBJ_ALIAS.put("java.lang.Long", "l");
|
||||
OBJ_ALIAS.put("java.lang.Double", "d");
|
||||
OBJ_ALIAS = Utils.newConstStringMap(
|
||||
Consts.CLASS_STRING, "str",
|
||||
Consts.CLASS_CLASS, "cls",
|
||||
Consts.CLASS_THROWABLE, "th",
|
||||
Consts.CLASS_OBJECT, "obj",
|
||||
"java.util.Iterator", "it",
|
||||
"java.lang.Boolean", "bool",
|
||||
"java.lang.Short", "sh",
|
||||
"java.lang.Integer", "num",
|
||||
"java.lang.Character", "ch",
|
||||
"java.lang.Byte", "b",
|
||||
"java.lang.Float", "f",
|
||||
"java.lang.Long", "l",
|
||||
"java.lang.Double", "d",
|
||||
"java.lang.StringBuilder", "sb",
|
||||
"java.lang.Exception", "exc");
|
||||
}
|
||||
|
||||
public NameGen(MethodNode mth, boolean fallback) {
|
||||
this.mth = mth;
|
||||
this.fallback = fallback;
|
||||
addNamesUsedInClass();
|
||||
}
|
||||
|
||||
public String assignArg(RegisterArg arg) {
|
||||
String name = makeArgName(arg);
|
||||
private void addNamesUsedInClass() {
|
||||
ClassNode parentClass = mth.getParentClass();
|
||||
for (FieldNode field : parentClass.getFields()) {
|
||||
varNames.add(field.getAlias());
|
||||
}
|
||||
for (ClassNode innerClass : parentClass.getInnerClasses()) {
|
||||
varNames.add(innerClass.getClassInfo().getAliasShortName());
|
||||
}
|
||||
// add all root package names to avoid collisions with full class names
|
||||
varNames.addAll(mth.root().getCacheStorage().getRootPkgs());
|
||||
}
|
||||
|
||||
public String assignArg(CodeVar var) {
|
||||
String name = makeArgName(var);
|
||||
if (fallback) {
|
||||
return name;
|
||||
}
|
||||
name = getUniqueVarName(name);
|
||||
arg.setName(name);
|
||||
var.setName(name);
|
||||
return name;
|
||||
}
|
||||
|
||||
@@ -99,39 +117,50 @@ public class NameGen {
|
||||
return r;
|
||||
}
|
||||
|
||||
private String makeArgName(RegisterArg arg) {
|
||||
private String makeArgName(CodeVar var) {
|
||||
if (fallback) {
|
||||
return getFallbackName(arg);
|
||||
return getFallbackName(var);
|
||||
}
|
||||
if (arg.isThis()) {
|
||||
if (var.isThis()) {
|
||||
return RegisterArg.THIS_ARG_NAME;
|
||||
}
|
||||
String name = arg.getName();
|
||||
String varName = name != null ? name : guessName(arg);
|
||||
String name = var.getName();
|
||||
String varName = name != null ? name : guessName(var);
|
||||
if (NameMapper.isReserved(varName)) {
|
||||
return varName + "R";
|
||||
varName = varName + 'R';
|
||||
}
|
||||
if (!NameMapper.isValidAndPrintable(varName)) {
|
||||
varName = getFallbackName(var);
|
||||
}
|
||||
return varName;
|
||||
}
|
||||
|
||||
private String getFallbackName(CodeVar var) {
|
||||
return getFallbackName(var.getSsaVars().get(0).getAssign());
|
||||
}
|
||||
|
||||
private String getFallbackName(RegisterArg arg) {
|
||||
return "r" + arg.getRegNum();
|
||||
}
|
||||
|
||||
private String guessName(RegisterArg arg) {
|
||||
SSAVar sVar = arg.getSVar();
|
||||
if (sVar != null && sVar.getName() == null) {
|
||||
RegisterArg assignArg = sVar.getAssign();
|
||||
InsnNode assignInsn = assignArg.getParentInsn();
|
||||
if (assignInsn != null) {
|
||||
String name = makeNameFromInsn(assignInsn);
|
||||
if (name != null && !NameMapper.isReserved(name)) {
|
||||
assignArg.setName(name);
|
||||
return name;
|
||||
private String guessName(CodeVar var) {
|
||||
List<SSAVar> ssaVars = var.getSsaVars();
|
||||
if (ssaVars != null && !ssaVars.isEmpty()) {
|
||||
// TODO: use all vars for better name generation
|
||||
SSAVar ssaVar = ssaVars.get(0);
|
||||
if (ssaVar != null && ssaVar.getName() == null) {
|
||||
RegisterArg assignArg = ssaVar.getAssign();
|
||||
InsnNode assignInsn = assignArg.getParentInsn();
|
||||
if (assignInsn != null) {
|
||||
String name = makeNameFromInsn(assignInsn);
|
||||
if (name != null && !NameMapper.isReserved(name)) {
|
||||
assignArg.setName(name);
|
||||
return name;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return makeNameForType(arg.getType());
|
||||
return makeNameForType(var.getType());
|
||||
}
|
||||
|
||||
private String makeNameForType(ArgType type) {
|
||||
@@ -149,12 +178,15 @@ public class NameGen {
|
||||
}
|
||||
|
||||
private String makeNameForObject(ArgType type) {
|
||||
if (type.isGenericType()) {
|
||||
return StringUtils.escape(type.getObject().toLowerCase());
|
||||
}
|
||||
if (type.isObject()) {
|
||||
String alias = getAliasForObject(type.getObject());
|
||||
if (alias != null) {
|
||||
return alias;
|
||||
}
|
||||
ClassInfo extClsInfo = ClassInfo.extCls(mth.root(), type);
|
||||
ClassInfo extClsInfo = ClassInfo.fromType(mth.root(), type);
|
||||
String shortName = extClsInfo.getShortName();
|
||||
String vName = fromName(shortName);
|
||||
if (vName != null) {
|
||||
@@ -227,16 +259,19 @@ public class NameGen {
|
||||
if (name.startsWith("get") || name.startsWith("set")) {
|
||||
return fromName(name.substring(3));
|
||||
}
|
||||
ArgType declType = callMth.getDeclClass().getAlias().getType();
|
||||
if ("iterator".equals(name)) {
|
||||
return "it";
|
||||
}
|
||||
ArgType declType = callMth.getDeclClass().getType();
|
||||
if ("toString".equals(name)) {
|
||||
return makeNameForType(declType);
|
||||
}
|
||||
if ("forName".equals(name) && declType.equals(ArgType.CLASS)) {
|
||||
return OBJ_ALIAS.get(Consts.CLASS_CLASS);
|
||||
}
|
||||
if (name.startsWith("to")) {
|
||||
return fromName(name.substring(2));
|
||||
}
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package jadx.core.codegen;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -11,7 +13,9 @@ import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.nodes.DeclareVariablesAttr;
|
||||
import jadx.core.dex.attributes.nodes.ForceReturnAttr;
|
||||
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.instructions.SwitchNode;
|
||||
import jadx.core.dex.instructions.args.CodeVar;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.NamedArg;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
@@ -33,6 +37,7 @@ import jadx.core.dex.regions.loops.ForLoop;
|
||||
import jadx.core.dex.regions.loops.LoopRegion;
|
||||
import jadx.core.dex.regions.loops.LoopType;
|
||||
import jadx.core.dex.trycatch.ExceptionHandler;
|
||||
import jadx.core.utils.BlockUtils;
|
||||
import jadx.core.utils.ErrorsCounter;
|
||||
import jadx.core.utils.RegionUtils;
|
||||
import jadx.core.utils.exceptions.CodegenException;
|
||||
@@ -73,7 +78,7 @@ public class RegionGen extends InsnGen {
|
||||
private void declareVars(CodeWriter code, IContainer cont) {
|
||||
DeclareVariablesAttr declVars = cont.get(AType.DECLARE_VARIABLES);
|
||||
if (declVars != null) {
|
||||
for (RegisterArg v : declVars.getVars()) {
|
||||
for (CodeVar v : declVars.getVars()) {
|
||||
code.startLine();
|
||||
declareVar(code, v);
|
||||
code.add(';');
|
||||
@@ -95,8 +100,12 @@ public class RegionGen extends InsnGen {
|
||||
}
|
||||
|
||||
private void makeSimpleBlock(IBlock block, CodeWriter code) throws CodegenException {
|
||||
if (block.contains(AFlag.DONT_GENERATE)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (InsnNode insn : block.getInstructions()) {
|
||||
if (!insn.contains(AFlag.SKIP)) {
|
||||
if (!insn.contains(AFlag.DONT_GENERATE)) {
|
||||
makeInsn(insn, code);
|
||||
}
|
||||
}
|
||||
@@ -112,6 +121,17 @@ public class RegionGen extends InsnGen {
|
||||
} else {
|
||||
code.attachSourceLine(region.getSourceLine());
|
||||
}
|
||||
if (attachInsns) {
|
||||
List<BlockNode> conditionBlocks = region.getConditionBlocks();
|
||||
if (!conditionBlocks.isEmpty()) {
|
||||
BlockNode blockNode = conditionBlocks.get(0);
|
||||
InsnNode lastInsn = BlockUtils.getLastInsn(blockNode);
|
||||
if (lastInsn != null) {
|
||||
code.attachLineAnnotation(lastInsn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
code.add("if (");
|
||||
new ConditionGen(this).add(code, region.getCondition());
|
||||
code.add(") {");
|
||||
@@ -119,7 +139,7 @@ public class RegionGen extends InsnGen {
|
||||
code.startLine('}');
|
||||
|
||||
IContainer els = region.getElseRegion();
|
||||
if (els != null && RegionUtils.notEmpty(els)) {
|
||||
if (RegionUtils.notEmpty(els)) {
|
||||
code.add(" else ");
|
||||
if (connectElseIf(code, els)) {
|
||||
return;
|
||||
@@ -230,7 +250,8 @@ public class RegionGen extends InsnGen {
|
||||
}
|
||||
|
||||
private CodeWriter makeSwitch(SwitchRegion sw, CodeWriter code) throws CodegenException {
|
||||
SwitchNode insn = (SwitchNode) sw.getHeader().getInstructions().get(0);
|
||||
SwitchNode insn = (SwitchNode) BlockUtils.getLastInsn(sw.getHeader());
|
||||
Objects.requireNonNull(insn, "Switch insn not found in header");
|
||||
InsnArg arg = insn.getArg(0);
|
||||
code.startLine("switch (");
|
||||
addArg(code, arg, false);
|
||||
@@ -256,7 +277,7 @@ public class RegionGen extends InsnGen {
|
||||
}
|
||||
}
|
||||
} else if (k instanceof Integer) {
|
||||
code.add(TypeGen.literalToString((Integer) k, arg.getType(), mth));
|
||||
code.add(TypeGen.literalToString((Integer) k, arg.getType(), mth, fallback));
|
||||
} else {
|
||||
throw new JadxRuntimeException("Unexpected key in switch: " + (k != null ? k.getClass() : null));
|
||||
}
|
||||
@@ -306,16 +327,24 @@ public class RegionGen extends InsnGen {
|
||||
return;
|
||||
}
|
||||
code.startLine("} catch (");
|
||||
if (handler.isCatchAll()) {
|
||||
code.add("Throwable");
|
||||
} else {
|
||||
Iterator<ClassInfo> it = handler.getCatchTypes().iterator();
|
||||
if (it.hasNext()) {
|
||||
useClass(code, it.next());
|
||||
}
|
||||
while (it.hasNext()) {
|
||||
code.add(" | ");
|
||||
useClass(code, it.next());
|
||||
}
|
||||
}
|
||||
code.add(' ');
|
||||
InsnArg arg = handler.getArg();
|
||||
if (arg instanceof RegisterArg) {
|
||||
declareVar(code, (RegisterArg) arg);
|
||||
RegisterArg reg = (RegisterArg) arg;
|
||||
code.add(mgen.getNameGen().assignArg(reg.getSVar().getCodeVar()));
|
||||
} else if (arg instanceof NamedArg) {
|
||||
if (handler.isCatchAll()) {
|
||||
code.add("Throwable");
|
||||
} else {
|
||||
useClass(code, handler.getCatchType());
|
||||
}
|
||||
code.add(' ');
|
||||
code.add(mgen.getNameGen().assignNamedArg((NamedArg) arg));
|
||||
}
|
||||
code.add(") {");
|
||||
|
||||
@@ -3,7 +3,6 @@ package jadx.core.codegen;
|
||||
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;
|
||||
@@ -34,22 +33,24 @@ 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());
|
||||
public static String literalToString(long lit, ArgType type, IDexNode dexNode, boolean fallback) {
|
||||
return literalToString(lit, type, dexNode.root().getStringUtils(), fallback);
|
||||
}
|
||||
|
||||
@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) {
|
||||
public static String literalToString(long lit, ArgType type, StringUtils stringUtils, boolean fallback) {
|
||||
if (type == null || !type.isTypeKnown()) {
|
||||
String n = Long.toString(lit);
|
||||
if (Math.abs(lit) > 100) {
|
||||
n += "; // 0x" + Long.toHexString(lit)
|
||||
+ " float:" + Float.intBitsToFloat((int) lit)
|
||||
+ " double:" + Double.longBitsToDouble(lit);
|
||||
if (fallback && Math.abs(lit) > 100) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(n).append("(0x").append(Long.toHexString(lit));
|
||||
if (type == null || type.contains(PrimitiveType.FLOAT)) {
|
||||
sb.append(", float:").append(Float.intBitsToFloat((int) lit));
|
||||
}
|
||||
if (type == null || type.contains(PrimitiveType.DOUBLE)) {
|
||||
sb.append(", double:").append(Double.longBitsToDouble(lit));
|
||||
}
|
||||
sb.append(')');
|
||||
return sb.toString();
|
||||
}
|
||||
return n;
|
||||
}
|
||||
@@ -64,11 +65,11 @@ public class TypeGen {
|
||||
}
|
||||
return stringUtils.unescapeChar(ch);
|
||||
case BYTE:
|
||||
return formatByte((byte) lit);
|
||||
return formatByte(lit);
|
||||
case SHORT:
|
||||
return formatShort((short) lit);
|
||||
return formatShort(lit);
|
||||
case INT:
|
||||
return formatInteger((int) lit);
|
||||
return formatInteger(lit);
|
||||
case LONG:
|
||||
return formatLong(lit);
|
||||
case FLOAT:
|
||||
@@ -89,34 +90,34 @@ public class TypeGen {
|
||||
}
|
||||
}
|
||||
|
||||
public static String formatShort(short s) {
|
||||
if (s == Short.MAX_VALUE) {
|
||||
public static String formatShort(long l) {
|
||||
if (l == Short.MAX_VALUE) {
|
||||
return "Short.MAX_VALUE";
|
||||
}
|
||||
if (s == Short.MIN_VALUE) {
|
||||
if (l == Short.MIN_VALUE) {
|
||||
return "Short.MIN_VALUE";
|
||||
}
|
||||
return "(short) " + Short.toString(s);
|
||||
return Long.toString(l);
|
||||
}
|
||||
|
||||
public static String formatByte(byte b) {
|
||||
if (b == Byte.MAX_VALUE) {
|
||||
public static String formatByte(long l) {
|
||||
if (l == Byte.MAX_VALUE) {
|
||||
return "Byte.MAX_VALUE";
|
||||
}
|
||||
if (b == Byte.MIN_VALUE) {
|
||||
if (l == Byte.MIN_VALUE) {
|
||||
return "Byte.MIN_VALUE";
|
||||
}
|
||||
return "(byte) " + Byte.toString(b);
|
||||
return Long.toString(l);
|
||||
}
|
||||
|
||||
public static String formatInteger(int i) {
|
||||
if (i == Integer.MAX_VALUE) {
|
||||
public static String formatInteger(long l) {
|
||||
if (l == Integer.MAX_VALUE) {
|
||||
return "Integer.MAX_VALUE";
|
||||
}
|
||||
if (i == Integer.MIN_VALUE) {
|
||||
if (l == Integer.MIN_VALUE) {
|
||||
return "Integer.MIN_VALUE";
|
||||
}
|
||||
return Integer.toString(i);
|
||||
return Long.toString(l);
|
||||
}
|
||||
|
||||
public static String formatLong(long l) {
|
||||
@@ -128,7 +129,7 @@ public class TypeGen {
|
||||
}
|
||||
String str = Long.toString(l);
|
||||
if (Math.abs(l) >= Integer.MAX_VALUE) {
|
||||
str += "L";
|
||||
str += 'L';
|
||||
}
|
||||
return str;
|
||||
}
|
||||
@@ -152,7 +153,7 @@ public class TypeGen {
|
||||
if (d == Double.MIN_NORMAL) {
|
||||
return "Double.MIN_NORMAL";
|
||||
}
|
||||
return Double.toString(d) + "d";
|
||||
return Double.toString(d) + 'd';
|
||||
}
|
||||
|
||||
public static String formatFloat(float f) {
|
||||
@@ -174,6 +175,6 @@ public class TypeGen {
|
||||
if (f == Float.MIN_NORMAL) {
|
||||
return "Float.MIN_NORMAL";
|
||||
}
|
||||
return Float.toString(f) + "f";
|
||||
return Float.toString(f) + 'f';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,224 @@
|
||||
package jadx.core.codegen.json;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import com.google.gson.FieldNamingPolicy;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
import jadx.api.CodePosition;
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.core.codegen.ClassGen;
|
||||
import jadx.core.codegen.CodeWriter;
|
||||
import jadx.core.codegen.MethodGen;
|
||||
import jadx.core.codegen.json.cls.JsonClass;
|
||||
import jadx.core.codegen.json.cls.JsonCodeLine;
|
||||
import jadx.core.codegen.json.cls.JsonField;
|
||||
import jadx.core.codegen.json.cls.JsonMethod;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
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.CodeGenUtils;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
public class JsonCodeGen {
|
||||
|
||||
private static final Gson GSON = new GsonBuilder()
|
||||
.setPrettyPrinting()
|
||||
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_DASHES)
|
||||
.disableHtmlEscaping()
|
||||
.create();
|
||||
|
||||
private final ClassNode cls;
|
||||
private final JadxArgs args;
|
||||
private final RootNode root;
|
||||
|
||||
public JsonCodeGen(ClassNode cls) {
|
||||
this.cls = cls;
|
||||
this.root = cls.root();
|
||||
this.args = root.getArgs();
|
||||
}
|
||||
|
||||
public String process() {
|
||||
JsonClass jsonCls = processCls(cls, null);
|
||||
return GSON.toJson(jsonCls);
|
||||
}
|
||||
|
||||
private JsonClass processCls(ClassNode cls, @Nullable ClassGen parentCodeGen) {
|
||||
ClassGen classGen;
|
||||
if (parentCodeGen == null) {
|
||||
classGen = new ClassGen(cls, args);
|
||||
} else {
|
||||
classGen = new ClassGen(cls, parentCodeGen);
|
||||
}
|
||||
ClassInfo classInfo = cls.getClassInfo();
|
||||
|
||||
JsonClass jsonCls = new JsonClass();
|
||||
jsonCls.setPkg(classInfo.getAliasPkg());
|
||||
jsonCls.setDex(cls.dex().getDexFile().getName());
|
||||
jsonCls.setName(classInfo.getFullName());
|
||||
if (classInfo.hasAlias()) {
|
||||
jsonCls.setAlias(classInfo.getAliasFullName());
|
||||
}
|
||||
jsonCls.setType(getClassTypeStr(cls));
|
||||
jsonCls.setAccessFlags(cls.getAccessFlags().rawValue());
|
||||
if (!Objects.equals(cls.getSuperClass(), ArgType.OBJECT)) {
|
||||
jsonCls.setSuperClass(getTypeAlias(cls.getSuperClass()));
|
||||
}
|
||||
if (!cls.getInterfaces().isEmpty()) {
|
||||
jsonCls.setInterfaces(Utils.collectionMap(cls.getInterfaces(), this::getTypeAlias));
|
||||
}
|
||||
|
||||
CodeWriter cw = new CodeWriter();
|
||||
CodeGenUtils.addComments(cw, cls);
|
||||
classGen.insertDecompilationProblems(cw, cls);
|
||||
classGen.addClassDeclaration(cw);
|
||||
jsonCls.setDeclaration(cw.finish().toString());
|
||||
|
||||
addFields(cls, jsonCls, classGen);
|
||||
addMethods(cls, jsonCls, classGen);
|
||||
addInnerClasses(cls, jsonCls, classGen);
|
||||
|
||||
if (!cls.getClassInfo().isInner()) {
|
||||
List<String> imports = Utils.collectionMap(classGen.getImports(), ClassInfo::getAliasFullName);
|
||||
Collections.sort(imports);
|
||||
jsonCls.setImports(imports);
|
||||
}
|
||||
return jsonCls;
|
||||
}
|
||||
|
||||
private void addInnerClasses(ClassNode cls, JsonClass jsonCls, ClassGen classGen) {
|
||||
List<ClassNode> innerClasses = cls.getInnerClasses();
|
||||
if (innerClasses.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
jsonCls.setInnerClasses(new ArrayList<>(innerClasses.size()));
|
||||
for (ClassNode innerCls : innerClasses) {
|
||||
if (innerCls.contains(AFlag.DONT_GENERATE)) {
|
||||
continue;
|
||||
}
|
||||
JsonClass innerJsonCls = processCls(innerCls, classGen);
|
||||
jsonCls.getInnerClasses().add(innerJsonCls);
|
||||
}
|
||||
}
|
||||
|
||||
private void addFields(ClassNode cls, JsonClass jsonCls, ClassGen classGen) {
|
||||
jsonCls.setFields(new ArrayList<>());
|
||||
for (FieldNode field : cls.getFields()) {
|
||||
if (field.contains(AFlag.DONT_GENERATE)) {
|
||||
continue;
|
||||
}
|
||||
JsonField jsonField = new JsonField();
|
||||
jsonField.setName(field.getName());
|
||||
if (field.getFieldInfo().hasAlias()) {
|
||||
jsonField.setAlias(field.getAlias());
|
||||
}
|
||||
|
||||
CodeWriter cw = new CodeWriter();
|
||||
classGen.addField(cw, field);
|
||||
jsonField.setDeclaration(cw.finish().toString());
|
||||
jsonField.setAccessFlags(field.getAccessFlags().rawValue());
|
||||
|
||||
jsonCls.getFields().add(jsonField);
|
||||
}
|
||||
}
|
||||
|
||||
private void addMethods(ClassNode cls, JsonClass jsonCls, ClassGen classGen) {
|
||||
jsonCls.setMethods(new ArrayList<>());
|
||||
for (MethodNode mth : cls.getMethods()) {
|
||||
if (mth.contains(AFlag.DONT_GENERATE)) {
|
||||
continue;
|
||||
}
|
||||
JsonMethod jsonMth = new JsonMethod();
|
||||
jsonMth.setName(mth.getName());
|
||||
if (mth.getMethodInfo().hasAlias()) {
|
||||
jsonMth.setAlias(mth.getAlias());
|
||||
}
|
||||
jsonMth.setSignature(mth.getMethodInfo().getShortId());
|
||||
jsonMth.setReturnType(getTypeAlias(mth.getReturnType()));
|
||||
jsonMth.setArguments(Utils.collectionMap(mth.getMethodInfo().getArgumentsTypes(), this::getTypeAlias));
|
||||
|
||||
MethodGen mthGen = new MethodGen(classGen, mth);
|
||||
CodeWriter cw = new CodeWriter();
|
||||
mthGen.addDefinition(cw);
|
||||
jsonMth.setDeclaration(cw.finish().toString());
|
||||
jsonMth.setAccessFlags(mth.getAccessFlags().rawValue());
|
||||
jsonMth.setLines(fillMthCode(mth, mthGen));
|
||||
jsonMth.setOffset("0x" + Long.toHexString(mth.getMethodCodeOffset()));
|
||||
jsonCls.getMethods().add(jsonMth);
|
||||
}
|
||||
}
|
||||
|
||||
private List<JsonCodeLine> fillMthCode(MethodNode mth, MethodGen mthGen) {
|
||||
if (mth.isNoCode()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
CodeWriter code = new CodeWriter();
|
||||
try {
|
||||
mthGen.addInstructions(code);
|
||||
} catch (Exception e) {
|
||||
throw new JadxRuntimeException("Method generation error", e);
|
||||
}
|
||||
code.finish();
|
||||
String codeStr = code.toString();
|
||||
if (codeStr.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
String[] lines = codeStr.split(CodeWriter.NL);
|
||||
Map<Integer, Integer> lineMapping = code.getLineMapping();
|
||||
Map<CodePosition, Object> annotations = code.getAnnotations();
|
||||
long mthCodeOffset = mth.getMethodCodeOffset() + 16;
|
||||
|
||||
int linesCount = lines.length;
|
||||
List<JsonCodeLine> codeLines = new ArrayList<>(linesCount);
|
||||
for (int i = 0; i < linesCount; i++) {
|
||||
String codeLine = lines[i];
|
||||
int line = i + 2;
|
||||
JsonCodeLine jsonCodeLine = new JsonCodeLine();
|
||||
jsonCodeLine.setCode(codeLine);
|
||||
jsonCodeLine.setSourceLine(lineMapping.get(line));
|
||||
Object obj = annotations.get(new CodePosition(line, 0));
|
||||
if (obj instanceof InsnNode) {
|
||||
int offset = ((InsnNode) obj).getOffset();
|
||||
jsonCodeLine.setOffset("0x" + Long.toHexString(mthCodeOffset + offset * 2));
|
||||
}
|
||||
codeLines.add(jsonCodeLine);
|
||||
}
|
||||
return codeLines;
|
||||
}
|
||||
|
||||
private String getTypeAlias(ArgType clsType) {
|
||||
if (Objects.equals(clsType, ArgType.OBJECT)) {
|
||||
return ArgType.OBJECT.getObject();
|
||||
}
|
||||
if (clsType.isObject()) {
|
||||
ClassInfo classInfo = ClassInfo.fromType(root, clsType);
|
||||
return classInfo.getAliasFullName();
|
||||
}
|
||||
return clsType.toString();
|
||||
}
|
||||
|
||||
private String getClassTypeStr(ClassNode cls) {
|
||||
if (cls.isEnum()) {
|
||||
return "enum";
|
||||
}
|
||||
if (cls.getAccessFlags().isInterface()) {
|
||||
return "interface";
|
||||
}
|
||||
return "class";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package jadx.core.codegen.json;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.Writer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.FieldNamingPolicy;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.core.codegen.json.mapping.JsonClsMapping;
|
||||
import jadx.core.codegen.json.mapping.JsonFieldMapping;
|
||||
import jadx.core.codegen.json.mapping.JsonMapping;
|
||||
import jadx.core.codegen.json.mapping.JsonMthMapping;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
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.utils.exceptions.JadxRuntimeException;
|
||||
import jadx.core.utils.files.FileUtils;
|
||||
|
||||
public class JsonMappingGen {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(JsonMappingGen.class);
|
||||
|
||||
private static final Gson GSON = new GsonBuilder()
|
||||
.setPrettyPrinting()
|
||||
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_DASHES)
|
||||
.disableHtmlEscaping()
|
||||
.create();
|
||||
|
||||
public static void dump(RootNode root) {
|
||||
JsonMapping mapping = new JsonMapping();
|
||||
fillMapping(mapping, root);
|
||||
|
||||
JadxArgs args = root.getArgs();
|
||||
File outDirSrc = args.getOutDirSrc().getAbsoluteFile();
|
||||
File mappingFile = new File(outDirSrc, "mapping.json");
|
||||
FileUtils.makeDirsForFile(mappingFile);
|
||||
try (Writer writer = new FileWriter(mappingFile)) {
|
||||
GSON.toJson(mapping, writer);
|
||||
LOG.info("Save mappings to {}", mappingFile.getAbsolutePath());
|
||||
} catch (Exception e) {
|
||||
throw new JadxRuntimeException("Failed to save mapping json", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void fillMapping(JsonMapping mapping, RootNode root) {
|
||||
List<ClassNode> classes = root.getClasses(true);
|
||||
mapping.setClasses(new ArrayList<>(classes.size()));
|
||||
for (ClassNode cls : classes) {
|
||||
ClassInfo classInfo = cls.getClassInfo();
|
||||
JsonClsMapping jsonCls = new JsonClsMapping();
|
||||
jsonCls.setName(classInfo.getRawName());
|
||||
jsonCls.setAlias(classInfo.getAliasFullName());
|
||||
jsonCls.setInner(classInfo.isInner());
|
||||
jsonCls.setJson(cls.getTopParentClass().getClassInfo().getAliasFullPath() + ".json");
|
||||
if (classInfo.isInner()) {
|
||||
jsonCls.setTopClass(cls.getTopParentClass().getClassInfo().getFullName());
|
||||
}
|
||||
addFields(cls, jsonCls);
|
||||
addMethods(cls, jsonCls);
|
||||
mapping.getClasses().add(jsonCls);
|
||||
}
|
||||
}
|
||||
|
||||
private static void addMethods(ClassNode cls, JsonClsMapping jsonCls) {
|
||||
List<MethodNode> methods = cls.getMethods();
|
||||
if (methods.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
jsonCls.setMethods(new ArrayList<>(methods.size()));
|
||||
for (MethodNode method : methods) {
|
||||
JsonMthMapping jsonMethod = new JsonMthMapping();
|
||||
MethodInfo methodInfo = method.getMethodInfo();
|
||||
jsonMethod.setSignature(methodInfo.getShortId());
|
||||
jsonMethod.setName(methodInfo.getName());
|
||||
jsonMethod.setAlias(methodInfo.getAlias());
|
||||
jsonMethod.setOffset("0x" + Long.toHexString(method.getMethodCodeOffset()));
|
||||
jsonCls.getMethods().add(jsonMethod);
|
||||
}
|
||||
}
|
||||
|
||||
private static void addFields(ClassNode cls, JsonClsMapping jsonCls) {
|
||||
List<FieldNode> fields = cls.getFields();
|
||||
if (fields.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
jsonCls.setFields(new ArrayList<>(fields.size()));
|
||||
for (FieldNode field : fields) {
|
||||
JsonFieldMapping jsonField = new JsonFieldMapping();
|
||||
jsonField.setName(field.getName());
|
||||
jsonField.setAlias(field.getAlias());
|
||||
jsonCls.getFields().add(jsonField);
|
||||
}
|
||||
}
|
||||
|
||||
private JsonMappingGen() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package jadx.core.codegen.json.cls;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
public class JsonClass extends JsonNode {
|
||||
@SerializedName("package")
|
||||
private String pkg;
|
||||
private String type; // class, interface, enum
|
||||
@SerializedName("extends")
|
||||
private String superClass;
|
||||
@SerializedName("implements")
|
||||
private List<String> interfaces;
|
||||
private String dex;
|
||||
|
||||
private List<JsonField> fields;
|
||||
private List<JsonMethod> methods;
|
||||
private List<JsonClass> innerClasses;
|
||||
|
||||
private List<String> imports;
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public String getSuperClass() {
|
||||
return superClass;
|
||||
}
|
||||
|
||||
public void setSuperClass(String superClass) {
|
||||
this.superClass = superClass;
|
||||
}
|
||||
|
||||
public List<String> getInterfaces() {
|
||||
return interfaces;
|
||||
}
|
||||
|
||||
public void setInterfaces(List<String> interfaces) {
|
||||
this.interfaces = interfaces;
|
||||
}
|
||||
|
||||
public List<JsonField> getFields() {
|
||||
return fields;
|
||||
}
|
||||
|
||||
public void setFields(List<JsonField> fields) {
|
||||
this.fields = fields;
|
||||
}
|
||||
|
||||
public List<JsonMethod> getMethods() {
|
||||
return methods;
|
||||
}
|
||||
|
||||
public void setMethods(List<JsonMethod> methods) {
|
||||
this.methods = methods;
|
||||
}
|
||||
|
||||
public List<JsonClass> getInnerClasses() {
|
||||
return innerClasses;
|
||||
}
|
||||
|
||||
public void setInnerClasses(List<JsonClass> innerClasses) {
|
||||
this.innerClasses = innerClasses;
|
||||
}
|
||||
|
||||
public String getPkg() {
|
||||
return pkg;
|
||||
}
|
||||
|
||||
public void setPkg(String pkg) {
|
||||
this.pkg = pkg;
|
||||
}
|
||||
|
||||
public String getDex() {
|
||||
return dex;
|
||||
}
|
||||
|
||||
public void setDex(String dex) {
|
||||
this.dex = dex;
|
||||
}
|
||||
|
||||
public List<String> getImports() {
|
||||
return imports;
|
||||
}
|
||||
|
||||
public void setImports(List<String> imports) {
|
||||
this.imports = imports;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package jadx.core.codegen.json.cls;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class JsonCodeLine {
|
||||
private String code;
|
||||
private String offset;
|
||||
private Integer sourceLine;
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public void setCode(String code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public String getOffset() {
|
||||
return offset;
|
||||
}
|
||||
|
||||
public void setOffset(String offset) {
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
public Integer getSourceLine() {
|
||||
return sourceLine;
|
||||
}
|
||||
|
||||
public void setSourceLine(@Nullable Integer sourceLine) {
|
||||
this.sourceLine = sourceLine;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package jadx.core.codegen.json.cls;
|
||||
|
||||
public class JsonField extends JsonNode {
|
||||
String type;
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package jadx.core.codegen.json.cls;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class JsonMethod extends JsonNode {
|
||||
private String signature;
|
||||
private String returnType;
|
||||
private List<String> arguments;
|
||||
private List<JsonCodeLine> lines;
|
||||
private String offset;
|
||||
|
||||
public String getSignature() {
|
||||
return signature;
|
||||
}
|
||||
|
||||
public void setSignature(String signature) {
|
||||
this.signature = signature;
|
||||
}
|
||||
|
||||
public String getReturnType() {
|
||||
return returnType;
|
||||
}
|
||||
|
||||
public void setReturnType(String returnType) {
|
||||
this.returnType = returnType;
|
||||
}
|
||||
|
||||
public List<String> getArguments() {
|
||||
return arguments;
|
||||
}
|
||||
|
||||
public void setArguments(List<String> arguments) {
|
||||
this.arguments = arguments;
|
||||
}
|
||||
|
||||
public List<JsonCodeLine> getLines() {
|
||||
return lines;
|
||||
}
|
||||
|
||||
public void setLines(List<JsonCodeLine> lines) {
|
||||
this.lines = lines;
|
||||
}
|
||||
|
||||
public String getOffset() {
|
||||
return offset;
|
||||
}
|
||||
|
||||
public void setOffset(String offset) {
|
||||
this.offset = offset;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package jadx.core.codegen.json.cls;
|
||||
|
||||
public class JsonNode {
|
||||
private String name;
|
||||
private String alias;
|
||||
private String declaration;
|
||||
private int accessFlags;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getAlias() {
|
||||
return alias;
|
||||
}
|
||||
|
||||
public void setAlias(String alias) {
|
||||
this.alias = alias;
|
||||
}
|
||||
|
||||
public String getDeclaration() {
|
||||
return declaration;
|
||||
}
|
||||
|
||||
public void setDeclaration(String declaration) {
|
||||
this.declaration = declaration;
|
||||
}
|
||||
|
||||
public int getAccessFlags() {
|
||||
return accessFlags;
|
||||
}
|
||||
|
||||
public void setAccessFlags(int accessFlags) {
|
||||
this.accessFlags = accessFlags;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package jadx.core.codegen.json.mapping;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class JsonClsMapping {
|
||||
private String name;
|
||||
private String alias;
|
||||
|
||||
private String json;
|
||||
private boolean inner;
|
||||
private String topClass;
|
||||
|
||||
private List<JsonFieldMapping> fields;
|
||||
private List<JsonMthMapping> methods;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getAlias() {
|
||||
return alias;
|
||||
}
|
||||
|
||||
public void setAlias(String alias) {
|
||||
this.alias = alias;
|
||||
}
|
||||
|
||||
public String getJson() {
|
||||
return json;
|
||||
}
|
||||
|
||||
public void setJson(String json) {
|
||||
this.json = json;
|
||||
}
|
||||
|
||||
public boolean isInner() {
|
||||
return inner;
|
||||
}
|
||||
|
||||
public void setInner(boolean inner) {
|
||||
this.inner = inner;
|
||||
}
|
||||
|
||||
public String getTopClass() {
|
||||
return topClass;
|
||||
}
|
||||
|
||||
public void setTopClass(String topClass) {
|
||||
this.topClass = topClass;
|
||||
}
|
||||
|
||||
public List<JsonFieldMapping> getFields() {
|
||||
return fields;
|
||||
}
|
||||
|
||||
public void setFields(List<JsonFieldMapping> fields) {
|
||||
this.fields = fields;
|
||||
}
|
||||
|
||||
public List<JsonMthMapping> getMethods() {
|
||||
return methods;
|
||||
}
|
||||
|
||||
public void setMethods(List<JsonMthMapping> methods) {
|
||||
this.methods = methods;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package jadx.core.codegen.json.mapping;
|
||||
|
||||
public class JsonFieldMapping {
|
||||
private String name;
|
||||
private String alias;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getAlias() {
|
||||
return alias;
|
||||
}
|
||||
|
||||
public void setAlias(String alias) {
|
||||
this.alias = alias;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package jadx.core.codegen.json.mapping;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class JsonMapping {
|
||||
private List<JsonClsMapping> classes;
|
||||
|
||||
public List<JsonClsMapping> getClasses() {
|
||||
return classes;
|
||||
}
|
||||
|
||||
public void setClasses(List<JsonClsMapping> classes) {
|
||||
this.classes = classes;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package jadx.core.codegen.json.mapping;
|
||||
|
||||
public class JsonMthMapping {
|
||||
private String signature;
|
||||
private String name;
|
||||
private String alias;
|
||||
private String offset;
|
||||
|
||||
public String getSignature() {
|
||||
return signature;
|
||||
}
|
||||
|
||||
public void setSignature(String signature) {
|
||||
this.signature = signature;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getAlias() {
|
||||
return alias;
|
||||
}
|
||||
|
||||
public void setAlias(String alias) {
|
||||
this.alias = alias;
|
||||
}
|
||||
|
||||
public String getOffset() {
|
||||
return offset;
|
||||
}
|
||||
|
||||
public void setOffset(String offset) {
|
||||
this.offset = offset;
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,15 @@
|
||||
package jadx.core.deobf;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -16,19 +17,21 @@ import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.info.FieldInfo;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
class DeobfPresets {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DeobfPresets.class);
|
||||
|
||||
private static final String MAP_FILE_CHARSET = "UTF-8";
|
||||
private static final Charset MAP_FILE_CHARSET = UTF_8;
|
||||
|
||||
private final Deobfuscator deobfuscator;
|
||||
private final File deobfMapFile;
|
||||
private final Path deobfMapFile;
|
||||
|
||||
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) {
|
||||
public DeobfPresets(Deobfuscator deobfuscator, Path deobfMapFile) {
|
||||
this.deobfuscator = deobfuscator;
|
||||
this.deobfMapFile = deobfMapFile;
|
||||
}
|
||||
@@ -37,12 +40,12 @@ class DeobfPresets {
|
||||
* Loads deobfuscator presets
|
||||
*/
|
||||
public void load() {
|
||||
if (!deobfMapFile.exists()) {
|
||||
if (!Files.exists(deobfMapFile)) {
|
||||
return;
|
||||
}
|
||||
LOG.info("Loading obfuscation map from: {}", deobfMapFile.getAbsoluteFile());
|
||||
LOG.info("Loading obfuscation map from: {}", deobfMapFile.toAbsolutePath());
|
||||
try {
|
||||
List<String> lines = FileUtils.readLines(deobfMapFile, MAP_FILE_CHARSET);
|
||||
List<String> lines = Files.readAllLines(deobfMapFile, MAP_FILE_CHARSET);
|
||||
for (String l : lines) {
|
||||
l = l.trim();
|
||||
if (l.isEmpty() || l.startsWith("#")) {
|
||||
@@ -65,7 +68,7 @@ class DeobfPresets {
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.error("Failed to load deobfuscation map file '{}'", deobfMapFile.getAbsolutePath(), e);
|
||||
LOG.error("Failed to load deobfuscation map file '{}'", deobfMapFile.toAbsolutePath(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,18 +82,18 @@ class DeobfPresets {
|
||||
|
||||
public void save(boolean forceSave) {
|
||||
try {
|
||||
if (deobfMapFile.exists()) {
|
||||
if (Files.exists(deobfMapFile)) {
|
||||
if (forceSave) {
|
||||
dumpMapping();
|
||||
} else {
|
||||
LOG.warn("Deobfuscation map file '{}' exists. Use command line option '--deobf-rewrite-cfg' to rewrite it",
|
||||
deobfMapFile.getAbsolutePath());
|
||||
deobfMapFile.toAbsolutePath());
|
||||
}
|
||||
} else {
|
||||
dumpMapping();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.error("Failed to load deobfuscation map file '{}'", deobfMapFile.getAbsolutePath(), e);
|
||||
LOG.error("Failed to load deobfuscation map file '{}'", deobfMapFile.toAbsolutePath(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,18 +115,20 @@ class DeobfPresets {
|
||||
for (DeobfClsInfo deobfClsInfo : deobfuscator.getClsMap().values()) {
|
||||
if (deobfClsInfo.getAlias() != null) {
|
||||
list.add(String.format("c %s = %s",
|
||||
deobfClsInfo.getCls().getClassInfo().getFullName(), deobfClsInfo.getAlias()));
|
||||
deobfClsInfo.getCls().getClassInfo().makeRawFullName(), deobfClsInfo.getAlias()));
|
||||
}
|
||||
}
|
||||
for (FieldInfo fld : deobfuscator.getFldMap().keySet()) {
|
||||
list.add(String.format("f %s = %s", fld.getFullId(), fld.getAlias()));
|
||||
list.add(String.format("f %s = %s", fld.getRawFullId(), fld.getAlias()));
|
||||
}
|
||||
for (MethodInfo mth : deobfuscator.getMthMap().keySet()) {
|
||||
list.add(String.format("m %s = %s", mth.getFullId(), mth.getAlias()));
|
||||
list.add(String.format("m %s = %s", mth.getRawFullId(), mth.getAlias()));
|
||||
}
|
||||
Collections.sort(list);
|
||||
FileUtils.writeLines(deobfMapFile, MAP_FILE_CHARSET, list);
|
||||
list.clear();
|
||||
Files.write(deobfMapFile, list, MAP_FILE_CHARSET);
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Deobfuscation map file saved as: {}", deobfMapFile);
|
||||
}
|
||||
}
|
||||
|
||||
private static void dfsPackageName(List<String> list, String prefix, PackageNode node) {
|
||||
@@ -136,15 +141,15 @@ class DeobfPresets {
|
||||
}
|
||||
|
||||
public String getForCls(ClassInfo cls) {
|
||||
return clsPresetMap.get(cls.getFullName());
|
||||
return clsPresetMap.get(cls.makeRawFullName());
|
||||
}
|
||||
|
||||
public String getForFld(FieldInfo fld) {
|
||||
return fldPresetMap.get(fld.getFullId());
|
||||
return fldPresetMap.get(fld.getRawFullId());
|
||||
}
|
||||
|
||||
public String getForMth(MethodInfo mth) {
|
||||
return mthPresetMap.get(mth.getFullId());
|
||||
return mthPresetMap.get(mth.getRawFullId());
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
package jadx.core.deobf;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -17,6 +18,7 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.nodes.SourceFileAttr;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
@@ -41,7 +43,7 @@ public class Deobfuscator {
|
||||
private final List<DexNode> dexNodes;
|
||||
private final DeobfPresets deobfPresets;
|
||||
|
||||
private final Map<ClassInfo, DeobfClsInfo> clsMap = new HashMap<>();
|
||||
private final Map<ClassInfo, DeobfClsInfo> clsMap = new LinkedHashMap<>();
|
||||
private final Map<FieldInfo, String> fldMap = new HashMap<>();
|
||||
private final Map<MethodInfo, String> mthMap = new HashMap<>();
|
||||
|
||||
@@ -61,7 +63,7 @@ public class Deobfuscator {
|
||||
private int fldIndex = 0;
|
||||
private int mthIndex = 0;
|
||||
|
||||
public Deobfuscator(JadxArgs args, @NotNull List<DexNode> dexNodes, File deobfMapFile) {
|
||||
public Deobfuscator(JadxArgs args, @NotNull List<DexNode> dexNodes, Path deobfMapFile) {
|
||||
this.args = args;
|
||||
this.dexNodes = dexNodes;
|
||||
|
||||
@@ -78,8 +80,20 @@ public class Deobfuscator {
|
||||
initIndexes();
|
||||
}
|
||||
process();
|
||||
}
|
||||
|
||||
public void savePresets() {
|
||||
deobfPresets.save(args.isDeobfuscationForceSave());
|
||||
clear();
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
deobfPresets.clear();
|
||||
clsMap.clear();
|
||||
fldMap.clear();
|
||||
mthMap.clear();
|
||||
|
||||
ovrd.clear();
|
||||
ovrdMap.clear();
|
||||
}
|
||||
|
||||
private void initIndexes() {
|
||||
@@ -128,8 +142,8 @@ public class Deobfuscator {
|
||||
}
|
||||
for (MethodInfo mth : o.getMethods()) {
|
||||
if (aliasToUse == null) {
|
||||
if (mth.isRenamed() && !mth.isAliasFromPreset()) {
|
||||
mth.setAlias(String.format("mo%d%s", id, makeName(mth.getName())));
|
||||
if (mth.hasAlias() && !mth.isAliasFromPreset()) {
|
||||
mth.setAlias(String.format("mo%d%s", id, prepareNamePart(mth.getName())));
|
||||
}
|
||||
aliasToUse = mth.getAlias();
|
||||
}
|
||||
@@ -140,16 +154,6 @@ public class Deobfuscator {
|
||||
}
|
||||
}
|
||||
|
||||
void clear() {
|
||||
deobfPresets.clear();
|
||||
clsMap.clear();
|
||||
fldMap.clear();
|
||||
mthMap.clear();
|
||||
|
||||
ovrd.clear();
|
||||
ovrdMap.clear();
|
||||
}
|
||||
|
||||
private void resolveOverriding(MethodNode mth) {
|
||||
Set<ClassNode> clsParents = new LinkedHashSet<>();
|
||||
collectClassHierarchy(mth.getParentClass(), clsParents);
|
||||
@@ -219,12 +223,28 @@ public class Deobfuscator {
|
||||
}
|
||||
|
||||
private void processClass(ClassNode cls) {
|
||||
if (isR(cls.getParentClass())) {
|
||||
return;
|
||||
}
|
||||
ClassInfo clsInfo = cls.getClassInfo();
|
||||
String fullName = getClassFullName(clsInfo);
|
||||
if (!fullName.equals(clsInfo.getFullName())) {
|
||||
clsInfo.rename(cls.dex().root(), fullName);
|
||||
DeobfClsInfo deobfClsInfo = clsMap.get(clsInfo);
|
||||
if (deobfClsInfo != null) {
|
||||
clsInfo.changeShortName(deobfClsInfo.getAlias());
|
||||
PackageNode pkgNode = deobfClsInfo.getPkg();
|
||||
if (!clsInfo.isInner() && pkgNode.hasAnyAlias()) {
|
||||
clsInfo.changePkg(pkgNode.getFullAlias());
|
||||
}
|
||||
} else if (!clsInfo.isInner()) {
|
||||
// check if package renamed
|
||||
PackageNode pkgNode = getPackageNode(clsInfo.getPackage(), false);
|
||||
if (pkgNode != null && pkgNode.hasAnyAlias()) {
|
||||
clsInfo.changePkg(pkgNode.getFullAlias());
|
||||
}
|
||||
}
|
||||
for (FieldNode field : cls.getFields()) {
|
||||
if (field.contains(AFlag.DONT_RENAME)) {
|
||||
continue;
|
||||
}
|
||||
renameField(field);
|
||||
}
|
||||
for (MethodNode mth : cls.getMethods()) {
|
||||
@@ -235,7 +255,7 @@ public class Deobfuscator {
|
||||
}
|
||||
}
|
||||
|
||||
public void renameField(FieldNode field) {
|
||||
private void renameField(FieldNode field) {
|
||||
FieldInfo fieldInfo = field.getFieldInfo();
|
||||
String alias = getFieldAlias(field);
|
||||
if (alias != null) {
|
||||
@@ -243,7 +263,11 @@ public class Deobfuscator {
|
||||
}
|
||||
}
|
||||
|
||||
public void renameMethod(MethodNode mth) {
|
||||
public void forceRenameField(FieldNode field) {
|
||||
field.getFieldInfo().setAlias(makeFieldAlias(field));
|
||||
}
|
||||
|
||||
private void renameMethod(MethodNode mth) {
|
||||
String alias = getMethodAlias(mth);
|
||||
if (alias != null) {
|
||||
mth.getMethodInfo().setAlias(alias);
|
||||
@@ -253,6 +277,13 @@ public class Deobfuscator {
|
||||
}
|
||||
}
|
||||
|
||||
public void forceRenameMethod(MethodNode mth) {
|
||||
mth.getMethodInfo().setAlias(makeMethodAlias(mth));
|
||||
if (mth.isVirtual()) {
|
||||
resolveOverriding(mth);
|
||||
}
|
||||
}
|
||||
|
||||
public void addPackagePreset(String origPkgName, String pkgAlias) {
|
||||
PackageNode pkg = getPackageNode(origPkgName, true);
|
||||
pkg.setAlias(pkgAlias);
|
||||
@@ -263,7 +294,8 @@ public class Deobfuscator {
|
||||
*
|
||||
* @param fullPkgName full package name
|
||||
* @param create if {@code true} then will create all absent objects
|
||||
* @return package node object or {@code null} if no package found and <b>create</b> set to {@code false}
|
||||
* @return package node object or {@code null} if no package found and <b>create</b> set to
|
||||
* {@code false}
|
||||
*/
|
||||
private PackageNode getPackageNode(String fullPkgName, boolean create) {
|
||||
if (fullPkgName.isEmpty() || fullPkgName.equals(CLASS_NAME_SEPARATOR)) {
|
||||
@@ -350,7 +382,7 @@ public class Deobfuscator {
|
||||
|
||||
if (alias == null) {
|
||||
String clsName = classInfo.getShortName();
|
||||
alias = String.format("C%04d%s", clsIndex++, makeName(clsName));
|
||||
alias = String.format("C%04d%s", clsIndex++, prepareNamePart(clsName));
|
||||
}
|
||||
PackageNode pkg = getPackageNode(classInfo.getPackage(), true);
|
||||
clsMap.put(classInfo, new DeobfClsInfo(this, cls, pkg, alias));
|
||||
@@ -372,7 +404,7 @@ public class Deobfuscator {
|
||||
} else if (name.endsWith(".kt")) {
|
||||
name = name.substring(0, name.length() - ".kt".length());
|
||||
}
|
||||
if (!NameMapper.isValidIdentifier(name) || NameMapper.isReserved(name)) {
|
||||
if (!NameMapper.isValidAndPrintable(name)) {
|
||||
return null;
|
||||
}
|
||||
for (DeobfClsInfo deobfClsInfo : clsMap.values()) {
|
||||
@@ -380,7 +412,7 @@ public class Deobfuscator {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
ClassNode otherCls = cls.dex().root().searchClassByName(cls.getPackage() + "." + name);
|
||||
ClassNode otherCls = cls.root().searchClassByName(cls.getPackage() + '.' + name);
|
||||
if (otherCls != null) {
|
||||
return null;
|
||||
}
|
||||
@@ -409,6 +441,9 @@ public class Deobfuscator {
|
||||
@Nullable
|
||||
private String getMethodAlias(MethodNode mth) {
|
||||
MethodInfo methodInfo = mth.getMethodInfo();
|
||||
if (methodInfo.isClassInit() || methodInfo.isConstructor()) {
|
||||
return null;
|
||||
}
|
||||
String alias = mthMap.get(methodInfo);
|
||||
if (alias != null) {
|
||||
return alias;
|
||||
@@ -426,13 +461,13 @@ public class Deobfuscator {
|
||||
}
|
||||
|
||||
public String makeFieldAlias(FieldNode field) {
|
||||
String alias = String.format("f%d%s", fldIndex++, makeName(field.getName()));
|
||||
String alias = String.format("f%d%s", fldIndex++, prepareNamePart(field.getName()));
|
||||
fldMap.put(field.getFieldInfo(), alias);
|
||||
return alias;
|
||||
}
|
||||
|
||||
public String makeMethodAlias(MethodNode mth) {
|
||||
String alias = String.format("m%d%s", mthIndex++, makeName(mth.getName()));
|
||||
String alias = String.format("m%d%s", mthIndex++, prepareNamePart(mth.getName()));
|
||||
mthMap.put(mth.getMethodInfo(), alias);
|
||||
return alias;
|
||||
}
|
||||
@@ -454,40 +489,21 @@ public class Deobfuscator {
|
||||
|
||||
String pkgName = pkg.getName();
|
||||
if (!pkg.hasAlias() && shouldRename(pkgName)) {
|
||||
String pkgAlias = String.format("p%03d%s", pkgIndex++, makeName(pkgName));
|
||||
String pkgAlias = String.format("p%03d%s", pkgIndex++, prepareNamePart(pkgName));
|
||||
pkg.setAlias(pkgAlias);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldRename(String s) {
|
||||
return s.length() > maxLength
|
||||
|| s.length() < minLength
|
||||
|| NameMapper.isReserved(s)
|
||||
|| !NameMapper.isAllCharsPrintable(s);
|
||||
int len = s.length();
|
||||
return len < minLength || len > maxLength;
|
||||
}
|
||||
|
||||
private String makeName(String name) {
|
||||
private String prepareNamePart(String name) {
|
||||
if (name.length() > maxLength) {
|
||||
return "x" + Integer.toHexString(name.hashCode());
|
||||
return 'x' + Integer.toHexString(name.hashCode());
|
||||
}
|
||||
if (NameMapper.isReserved(name)) {
|
||||
return name;
|
||||
}
|
||||
if (!NameMapper.isAllCharsPrintable(name)) {
|
||||
return removeInvalidChars(name);
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
private String removeInvalidChars(String name) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < name.length(); i++) {
|
||||
int ch = name.charAt(i);
|
||||
if (NameMapper.isPrintableChar(ch)) {
|
||||
sb.append((char) ch);
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
return NameMapper.removeInvalidCharsMiddle(name);
|
||||
}
|
||||
|
||||
private void dumpClassAlias(ClassNode cls) {
|
||||
@@ -527,10 +543,7 @@ public class Deobfuscator {
|
||||
}
|
||||
|
||||
private String getClassFullName(ClassNode cls) {
|
||||
return getClassFullName(cls.getClassInfo());
|
||||
}
|
||||
|
||||
private String getClassFullName(ClassInfo clsInfo) {
|
||||
ClassInfo clsInfo = cls.getClassInfo();
|
||||
DeobfClsInfo deobfClsInfo = clsMap.get(clsInfo);
|
||||
if (deobfClsInfo != null) {
|
||||
return deobfClsInfo.getFullName();
|
||||
@@ -553,4 +566,27 @@ public class Deobfuscator {
|
||||
public PackageNode getRootPackage() {
|
||||
return rootPackage;
|
||||
}
|
||||
|
||||
private static boolean isR(ClassNode cls) {
|
||||
if (!cls.getClassInfo().getShortName().equals("R")) {
|
||||
return false;
|
||||
}
|
||||
if (!cls.getMethods().isEmpty() || !cls.getFields().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
for (ClassNode inner : cls.getInnerClasses()) {
|
||||
for (MethodNode m : inner.getMethods()) {
|
||||
if (!m.getMethodInfo().isConstructor() && !m.getMethodInfo().isClassInit()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (FieldNode field : cls.getFields()) {
|
||||
ArgType type = field.getType();
|
||||
if (type != ArgType.INT && (!type.isArray() || type.getArrayElement() != ArgType.INT)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,9 +69,7 @@ public class NameMapper {
|
||||
"try",
|
||||
"void",
|
||||
"volatile",
|
||||
"while"
|
||||
)
|
||||
);
|
||||
"while"));
|
||||
|
||||
public static boolean isReserved(String str) {
|
||||
return RESERVED_NAMES.contains(str);
|
||||
@@ -79,14 +77,26 @@ public class NameMapper {
|
||||
|
||||
public static boolean isValidIdentifier(String str) {
|
||||
return notEmpty(str)
|
||||
&& VALID_JAVA_IDENTIFIER.matcher(str).matches()
|
||||
&& isAllCharsPrintable(str);
|
||||
&& !isReserved(str)
|
||||
&& VALID_JAVA_IDENTIFIER.matcher(str).matches();
|
||||
}
|
||||
|
||||
public static boolean isValidFullIdentifier(String str) {
|
||||
return notEmpty(str)
|
||||
&& VALID_JAVA_FULL_IDENTIFIER.matcher(str).matches()
|
||||
&& isAllCharsPrintable(str);
|
||||
&& !isReserved(str)
|
||||
&& VALID_JAVA_FULL_IDENTIFIER.matcher(str).matches();
|
||||
}
|
||||
|
||||
public static boolean isValidAndPrintable(String str) {
|
||||
return isValidIdentifier(str) && isAllCharsPrintable(str);
|
||||
}
|
||||
|
||||
public static boolean isValidIdentifierStart(int codePoint) {
|
||||
return Character.isJavaIdentifierStart(codePoint);
|
||||
}
|
||||
|
||||
public static boolean isValidIdentifierPart(int codePoint) {
|
||||
return Character.isJavaIdentifierPart(codePoint);
|
||||
}
|
||||
|
||||
public static boolean isPrintableChar(int c) {
|
||||
@@ -103,6 +113,53 @@ public class NameMapper {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return modified string with removed:
|
||||
* <p>
|
||||
* <ul>
|
||||
* <li>not printable chars (including unicode)
|
||||
* <li>chars not valid for java identifier part
|
||||
* </ul>
|
||||
* <p>
|
||||
* Note: this 'middle' method must be used with prefixed string:
|
||||
* <p>
|
||||
* <ul>
|
||||
* <li>can leave invalid chars for java identifier start (i.e numbers)
|
||||
* <li>result not checked for reserved words
|
||||
* </ul>
|
||||
* <p>
|
||||
*/
|
||||
public static String removeInvalidCharsMiddle(String name) {
|
||||
if (isValidIdentifier(name) && isAllCharsPrintable(name)) {
|
||||
return name;
|
||||
}
|
||||
int len = name.length();
|
||||
StringBuilder sb = new StringBuilder(len);
|
||||
for (int i = 0; i < len; i++) {
|
||||
int codePoint = name.codePointAt(i);
|
||||
if (isPrintableChar(codePoint) && isValidIdentifierPart(codePoint)) {
|
||||
sb.append((char) codePoint);
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return string with removed invalid chars, see {@link #removeInvalidCharsMiddle}
|
||||
* <p>
|
||||
* Prepend prefix if first char is not valid as java identifier start char.
|
||||
*/
|
||||
public static String removeInvalidChars(String name, String prefix) {
|
||||
String result = removeInvalidCharsMiddle(name);
|
||||
if (!result.isEmpty()) {
|
||||
int codePoint = result.codePointAt(0);
|
||||
if (!isValidIdentifierStart(codePoint)) {
|
||||
return prefix + result;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private NameMapper() {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,6 +58,16 @@ public class PackageNode {
|
||||
return packageAlias != null;
|
||||
}
|
||||
|
||||
public boolean hasAnyAlias() {
|
||||
if (hasAlias()) {
|
||||
return true;
|
||||
}
|
||||
if (parentPackage != this) {
|
||||
return parentPackage.hasAnyAlias();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public String getFullAlias() {
|
||||
if (cachedPackageFullAlias == null) {
|
||||
Deque<PackageNode> pp = getParentPackages();
|
||||
@@ -127,4 +137,9 @@ public class PackageNode {
|
||||
}
|
||||
return pp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return packageAlias;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,26 +8,40 @@ public enum AFlag {
|
||||
LOOP_END,
|
||||
|
||||
SYNTHETIC,
|
||||
FINAL, // SSAVar attribute for make var final
|
||||
|
||||
RETURN, // block contains only return instruction
|
||||
ORIG_RETURN,
|
||||
|
||||
DECLARE_VAR,
|
||||
DONT_WRAP,
|
||||
|
||||
DONT_SHRINK,
|
||||
DONT_INLINE,
|
||||
DONT_GENERATE,
|
||||
SKIP,
|
||||
REMOVE,
|
||||
DONT_GENERATE, // process as usual, but don't output to generated code
|
||||
RESTART_CODEGEN,
|
||||
DONT_RENAME, // do not rename during deobfuscation
|
||||
REMOVE, // can be completely removed
|
||||
ADDED_TO_REGION,
|
||||
|
||||
FINALLY_INSNS,
|
||||
|
||||
SKIP_FIRST_ARG,
|
||||
SKIP_ARG, // skip argument in invoke call
|
||||
ANONYMOUS_CONSTRUCTOR,
|
||||
ANONYMOUS_CLASS,
|
||||
|
||||
THIS,
|
||||
|
||||
/**
|
||||
* RegisterArg attribute for method arguments
|
||||
*/
|
||||
METHOD_ARGUMENT,
|
||||
|
||||
/**
|
||||
* Type of RegisterArg or SSAVar can't be changed
|
||||
*/
|
||||
IMMUTABLE_TYPE,
|
||||
|
||||
CUSTOM_DECLARE, // variable for this register don't need declaration
|
||||
DECLARE_VAR,
|
||||
|
||||
ELSE_IF_CHAIN,
|
||||
|
||||
WRAPPED,
|
||||
@@ -35,5 +49,7 @@ public enum AFlag {
|
||||
|
||||
FALL_THROUGH,
|
||||
|
||||
EXPLICIT_GENERICS,
|
||||
|
||||
INCONSISTENT_CODE, // warning about incorrect decompilation
|
||||
}
|
||||
|
||||
@@ -10,12 +10,14 @@ 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.JadxError;
|
||||
import jadx.core.dex.attributes.nodes.JadxWarn;
|
||||
import jadx.core.dex.attributes.nodes.JumpInfo;
|
||||
import jadx.core.dex.attributes.nodes.LocalVarsDebugInfoAttr;
|
||||
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.RegDebugInfoAttr;
|
||||
import jadx.core.dex.attributes.nodes.RenameReasonAttr;
|
||||
import jadx.core.dex.attributes.nodes.SourceFileAttr;
|
||||
import jadx.core.dex.nodes.parser.FieldInitAttr;
|
||||
import jadx.core.dex.trycatch.CatchAttr;
|
||||
@@ -34,8 +36,9 @@ public class AType<T extends IAttribute> {
|
||||
public static final AType<AttrList<LoopInfo>> LOOP = new AType<>();
|
||||
public static final AType<AttrList<EdgeInsnAttr>> EDGE_INSN = new AType<>();
|
||||
|
||||
public static final AType<AttrList<JadxError>> JADX_ERROR = new AType<>();
|
||||
public static final AType<AttrList<JadxWarn>> JADX_WARN = new AType<>();
|
||||
public static final AType<AttrList<JadxError>> JADX_ERROR = new AType<>(); // code failed to decompile completely
|
||||
public static final AType<AttrList<String>> JADX_WARN = new AType<>(); // mark code as inconsistent (code can be viewed)
|
||||
public static final AType<AttrList<String>> COMMENTS = new AType<>(); // any additional info about decompilation
|
||||
|
||||
public static final AType<ExcHandlerAttr> EXC_HANDLER = new AType<>();
|
||||
public static final AType<CatchAttr> CATCH_BLOCK = new AType<>();
|
||||
@@ -53,4 +56,11 @@ public class AType<T extends IAttribute> {
|
||||
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<>();
|
||||
public static final AType<RenameReasonAttr> RENAME_REASON = new AType<>();
|
||||
|
||||
// method
|
||||
public static final AType<LocalVarsDebugInfoAttr> LOCAL_VARS_DEBUG_INFO = new AType<>();
|
||||
|
||||
// registers
|
||||
public static final AType<RegDebugInfoAttr> REG_DEBUG_INFO = new AType<>();
|
||||
}
|
||||
|
||||
@@ -25,6 +25,6 @@ public class AttrList<T> implements IAttribute {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return Utils.listToString(list);
|
||||
return Utils.listToString(list, "\n");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +42,12 @@ public abstract class AttrNode implements IAttributeNode {
|
||||
return store;
|
||||
}
|
||||
|
||||
private void unloadIfEmpty() {
|
||||
if (storage.isEmpty() && storage != EMPTY_ATTR_STORAGE) {
|
||||
storage = EMPTY_ATTR_STORAGE;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(AFlag flag) {
|
||||
return storage.contains(flag);
|
||||
@@ -70,21 +76,25 @@ public abstract class AttrNode implements IAttributeNode {
|
||||
@Override
|
||||
public void remove(AFlag flag) {
|
||||
storage.remove(flag);
|
||||
unloadIfEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends IAttribute> void remove(AType<T> type) {
|
||||
storage.remove(type);
|
||||
unloadIfEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAttr(IAttribute attr) {
|
||||
storage.remove(attr);
|
||||
unloadIfEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearAttributes() {
|
||||
storage.clear();
|
||||
unloadIfEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -121,6 +121,7 @@ public class AttributeStorage {
|
||||
if (list.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
return "A:{" + Utils.listToString(list) + "}";
|
||||
list.sort(String::compareTo);
|
||||
return "A[" + Utils.listToString(list) + ']';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,18 +34,22 @@ public final class EmptyAttrStorage extends AttributeStorage {
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
// ignore
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(AFlag flag) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends IAttribute> void remove(AType<T> type) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(IAttribute attr) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -42,7 +42,6 @@ public class Annotation {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Annotation[" + visibility + ", " + atype + ", " + values + "]";
|
||||
return "Annotation[" + visibility + ", " + atype + ", " + values + ']';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import jadx.core.utils.Utils;
|
||||
|
||||
public class AnnotationsList implements IAttribute {
|
||||
|
||||
public static final AnnotationsList EMPTY = new AnnotationsList(Collections.<Annotation>emptyList());
|
||||
public static final AnnotationsList EMPTY = new AnnotationsList(Collections.emptyList());
|
||||
|
||||
private final Map<String, Annotation> map;
|
||||
|
||||
@@ -48,5 +48,4 @@ public class AnnotationsList implements IAttribute {
|
||||
public String toString() {
|
||||
return Utils.listToString(map.values());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -28,5 +28,4 @@ public class MethodParameters implements IAttribute {
|
||||
public String toString() {
|
||||
return Utils.listToString(paramList);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package jadx.core.dex.attributes.nodes;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.ArrayList;
|
||||
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.dex.instructions.args.CodeVar;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
/**
|
||||
@@ -13,13 +13,13 @@ import jadx.core.utils.Utils;
|
||||
*/
|
||||
public class DeclareVariablesAttr implements IAttribute {
|
||||
|
||||
private final List<RegisterArg> vars = new LinkedList<>();
|
||||
private final List<CodeVar> vars = new ArrayList<>();
|
||||
|
||||
public Iterable<RegisterArg> getVars() {
|
||||
public Iterable<CodeVar> getVars() {
|
||||
return vars;
|
||||
}
|
||||
|
||||
public void addVar(RegisterArg arg) {
|
||||
public void addVar(CodeVar arg) {
|
||||
vars.add(arg);
|
||||
}
|
||||
|
||||
|
||||
@@ -43,6 +43,6 @@ public class EdgeInsnAttr implements IAttribute {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "EDGE_INSN: " + start + "->" + end + " " + insn;
|
||||
return "EDGE_INSN: " + start + "->" + end + ' ' + insn;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,5 +78,4 @@ public class EnumClassAttr implements IAttribute {
|
||||
public String toString() {
|
||||
return "Enum fields: " + fields;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -44,6 +44,6 @@ public class FieldReplaceAttr implements IAttribute {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "REPLACE: " + replaceType + " " + replaceObj;
|
||||
return "REPLACE: " + replaceType + ' ' + replaceObj;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,5 +26,4 @@ public class ForceReturnAttr implements IAttribute {
|
||||
public String toString() {
|
||||
return "FORCE_RETURN " + Utils.listToString(returnInsn.getArguments());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
package jadx.core.dex.attributes.nodes;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
public class JadxError {
|
||||
public class JadxError implements Comparable<JadxError> {
|
||||
|
||||
private final String error;
|
||||
private final Throwable cause;
|
||||
|
||||
public JadxError(Throwable cause) {
|
||||
this(null, cause);
|
||||
}
|
||||
|
||||
public JadxError(String error, Throwable cause) {
|
||||
this.error = error;
|
||||
this.error = Objects.requireNonNull(error);
|
||||
this.cause = cause;
|
||||
}
|
||||
|
||||
@@ -24,6 +24,28 @@ public class JadxError {
|
||||
return cause;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull JadxError o) {
|
||||
return this.error.compareTo(o.getError());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
JadxError other = (JadxError) o;
|
||||
return error.equals(other.error);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return error.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder str = new StringBuilder();
|
||||
@@ -34,9 +56,9 @@ public class JadxError {
|
||||
}
|
||||
if (cause != null) {
|
||||
str.append(cause.getClass());
|
||||
str.append(":");
|
||||
str.append(':');
|
||||
str.append(cause.getMessage());
|
||||
str.append("\n");
|
||||
str.append('\n');
|
||||
str.append(Utils.getStackTrace(cause));
|
||||
}
|
||||
return str.toString();
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package jadx.core.dex.attributes.nodes;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.IAttribute;
|
||||
import jadx.core.dex.visitors.debuginfo.LocalVar;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
public class LocalVarsDebugInfoAttr implements IAttribute {
|
||||
private final List<LocalVar> localVars;
|
||||
|
||||
public LocalVarsDebugInfoAttr(List<LocalVar> localVars) {
|
||||
this.localVars = localVars;
|
||||
}
|
||||
|
||||
public List<LocalVar> getLocalVars() {
|
||||
return localVars;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AType<LocalVarsDebugInfoAttr> getType() {
|
||||
return AType.LOCAL_VARS_DEBUG_INFO;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Debug Info:\n " + Utils.listToString(localVars, "\n ");
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,10 @@ public class PhiListAttr implements IAttribute {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("PHI: ");
|
||||
for (PhiInsn phiInsn : list) {
|
||||
sb.append('r').append(phiInsn.getResult().getRegNum()).append(" ");
|
||||
sb.append('r').append(phiInsn.getResult().getRegNum()).append(' ');
|
||||
}
|
||||
for (PhiInsn phiInsn : list) {
|
||||
sb.append("\n ").append(phiInsn).append(' ').append(phiInsn.getAttributesString());
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
package jadx.core.dex.attributes.nodes;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.IAttribute;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.visitors.debuginfo.LocalVar;
|
||||
|
||||
public class RegDebugInfoAttr implements IAttribute {
|
||||
|
||||
private final ArgType type;
|
||||
private final String name;
|
||||
|
||||
public RegDebugInfoAttr(LocalVar var) {
|
||||
this(var.getType(), var.getName());
|
||||
}
|
||||
|
||||
public RegDebugInfoAttr(ArgType type, String name) {
|
||||
this.type = type;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public ArgType getRegType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AType<RegDebugInfoAttr> getType() {
|
||||
return AType.REG_DEBUG_INFO;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
RegDebugInfoAttr that = (RegDebugInfoAttr) o;
|
||||
return Objects.equals(type, that.type) && Objects.equals(name, that.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(type, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "D('" + name + "' " + type + ')';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package jadx.core.dex.attributes.nodes;
|
||||
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.AttrNode;
|
||||
import jadx.core.dex.attributes.IAttribute;
|
||||
|
||||
public class RenameReasonAttr implements IAttribute {
|
||||
|
||||
private String description;
|
||||
|
||||
public RenameReasonAttr() {
|
||||
this.description = "";
|
||||
}
|
||||
|
||||
public RenameReasonAttr(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public RenameReasonAttr(AttrNode node) {
|
||||
RenameReasonAttr renameReasonAttr = node.get(AType.RENAME_REASON);
|
||||
if (renameReasonAttr != null) {
|
||||
this.description = renameReasonAttr.description;
|
||||
} else {
|
||||
this.description = "";
|
||||
}
|
||||
}
|
||||
|
||||
public RenameReasonAttr(AttrNode node, boolean notValid, boolean notPrintable) {
|
||||
this(node);
|
||||
if (notValid) {
|
||||
notValid();
|
||||
}
|
||||
if (notPrintable) {
|
||||
notPrintable();
|
||||
}
|
||||
}
|
||||
|
||||
public RenameReasonAttr notValid() {
|
||||
return append("not valid java name");
|
||||
}
|
||||
|
||||
public RenameReasonAttr notPrintable() {
|
||||
return append("contains not printable characters");
|
||||
}
|
||||
|
||||
public RenameReasonAttr append(String reason) {
|
||||
if (description.isEmpty()) {
|
||||
description += reason;
|
||||
} else {
|
||||
description += " and " + reason;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AType<RenameReasonAttr> getType() {
|
||||
return AType.RENAME_REASON;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "RENAME_REASON:" + description;
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import jadx.core.Consts;
|
||||
|
||||
public class AccessInfo {
|
||||
|
||||
public static final int VISIBILITY_FLAGS = AccessFlags.ACC_PUBLIC | AccessFlags.ACC_PROTECTED | AccessFlags.ACC_PRIVATE;
|
||||
private final int accFlags;
|
||||
|
||||
public enum AFType {
|
||||
@@ -30,11 +31,24 @@ public class AccessInfo {
|
||||
return this;
|
||||
}
|
||||
|
||||
public AccessInfo add(int flag) {
|
||||
if (!containsFlag(flag)) {
|
||||
return new AccessInfo(accFlags | flag, type);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public AccessInfo changeVisibility(int flag) {
|
||||
int currentVisFlags = accFlags & VISIBILITY_FLAGS;
|
||||
if (currentVisFlags == flag) {
|
||||
return this;
|
||||
}
|
||||
int unsetAllVisFlags = accFlags & ~VISIBILITY_FLAGS;
|
||||
return new AccessInfo(unsetAllVisFlags | flag, type);
|
||||
}
|
||||
|
||||
public AccessInfo getVisibility() {
|
||||
int f = accFlags & AccessFlags.ACC_PUBLIC
|
||||
| accFlags & AccessFlags.ACC_PROTECTED
|
||||
| accFlags & AccessFlags.ACC_PRIVATE;
|
||||
return new AccessInfo(f, type);
|
||||
return new AccessInfo(accFlags & VISIBILITY_FLAGS, type);
|
||||
}
|
||||
|
||||
public boolean isPublic() {
|
||||
@@ -187,8 +201,12 @@ public class AccessInfo {
|
||||
}
|
||||
}
|
||||
|
||||
public int rawValue() {
|
||||
return accFlags;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "AccessInfo: " + type + " 0x" + Integer.toHexString(accFlags) + " (" + rawString() + ")";
|
||||
return "AccessInfo: " + type + " 0x" + Integer.toHexString(accFlags) + " (" + rawString() + ')';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
package jadx.core.dex.info;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
class ClassAliasInfo {
|
||||
private final String shortName;
|
||||
@Nullable
|
||||
private final String pkg;
|
||||
@Nullable
|
||||
private String fullName;
|
||||
|
||||
ClassAliasInfo(@Nullable String pkg, String shortName) {
|
||||
this.pkg = pkg;
|
||||
this.shortName = shortName;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getPkg() {
|
||||
return pkg;
|
||||
}
|
||||
|
||||
public String getShortName() {
|
||||
return shortName;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getFullName() {
|
||||
return fullName;
|
||||
}
|
||||
|
||||
public void setFullName(@Nullable String fullName) {
|
||||
this.fullName = fullName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Alias{" + shortName + ", pkg=" + pkg + ", fullName=" + fullName + '}';
|
||||
}
|
||||
}
|
||||
@@ -1,47 +1,40 @@
|
||||
package jadx.core.dex.info;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
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;
|
||||
|
||||
public final class ClassInfo {
|
||||
|
||||
public final class ClassInfo implements Comparable<ClassInfo> {
|
||||
private final ArgType type;
|
||||
private String pkg;
|
||||
private String name;
|
||||
@Nullable("for inner classes")
|
||||
private String pkg;
|
||||
private String fullName;
|
||||
// for inner class not equals null
|
||||
@Nullable
|
||||
private ClassInfo parentClass;
|
||||
// class info after rename (deobfuscation)
|
||||
private ClassInfo alias;
|
||||
|
||||
private ClassInfo(RootNode root, ArgType type) {
|
||||
this(root, type, true);
|
||||
}
|
||||
@Nullable
|
||||
private ClassAliasInfo alias;
|
||||
|
||||
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(root, inner);
|
||||
splitAndApplyNames(root, type, inner);
|
||||
}
|
||||
|
||||
public static ClassInfo fromType(RootNode root, ArgType type) {
|
||||
if (type.isArray()) {
|
||||
type = ArgType.OBJECT;
|
||||
}
|
||||
ClassInfo cls = root.getInfoStorage().getCls(type);
|
||||
ArgType clsType = checkClassType(type);
|
||||
ClassInfo cls = root.getInfoStorage().getCls(clsType);
|
||||
if (cls != null) {
|
||||
return cls;
|
||||
}
|
||||
cls = new ClassInfo(root, type);
|
||||
return root.getInfoStorage().putCls(cls);
|
||||
ClassInfo newClsInfo = new ClassInfo(root, clsType, true);
|
||||
return root.getInfoStorage().putCls(newClsInfo);
|
||||
}
|
||||
|
||||
public static ClassInfo fromDex(DexNode dex, int clsIndex) {
|
||||
@@ -55,67 +48,144 @@ public final class ClassInfo {
|
||||
return fromType(root, ArgType.object(clsName));
|
||||
}
|
||||
|
||||
public static ClassInfo extCls(RootNode root, ArgType type) {
|
||||
ClassInfo classInfo = fromName(root, type.getObject());
|
||||
return classInfo.alias;
|
||||
private static ArgType checkClassType(ArgType type) {
|
||||
if (type == null) {
|
||||
throw new JadxRuntimeException("Null class type");
|
||||
}
|
||||
if (type.isArray()) {
|
||||
// TODO: check case with method declared in array class like ( clone in int[])
|
||||
return ArgType.OBJECT;
|
||||
}
|
||||
if (!type.isObject() || type.isGenericType()) {
|
||||
throw new JadxRuntimeException("Not class type: " + type);
|
||||
}
|
||||
if (type.isGeneric()) {
|
||||
return ArgType.object(type.getObject());
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
public void rename(RootNode root, String fullName) {
|
||||
ClassInfo newAlias = new ClassInfo(root, ArgType.object(fullName), isInner());
|
||||
if (!alias.getFullName().equals(newAlias.getFullName())) {
|
||||
public void changeShortName(String aliasName) {
|
||||
if (!Objects.equals(name, aliasName)) {
|
||||
ClassAliasInfo newAlias = new ClassAliasInfo(getAliasPkg(), aliasName);
|
||||
fillAliasFullName(newAlias);
|
||||
this.alias = newAlias;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isRenamed() {
|
||||
return alias != this;
|
||||
public void changePkg(String aliasPkg) {
|
||||
if (isInner()) {
|
||||
throw new JadxRuntimeException("Can't change package for inner class");
|
||||
}
|
||||
if (!Objects.equals(getAliasPkg(), aliasPkg)) {
|
||||
ClassAliasInfo newAlias = new ClassAliasInfo(aliasPkg, getAliasShortName());
|
||||
fillAliasFullName(newAlias);
|
||||
this.alias = newAlias;
|
||||
}
|
||||
}
|
||||
|
||||
public ClassInfo getAlias() {
|
||||
return alias;
|
||||
private void fillAliasFullName(ClassAliasInfo alias) {
|
||||
if (parentClass == null) {
|
||||
alias.setFullName(makeFullClsName(alias.getPkg(), alias.getShortName(), null, true, false));
|
||||
}
|
||||
}
|
||||
|
||||
private void splitNames(RootNode root, boolean canBeInner) {
|
||||
public String getAliasPkg() {
|
||||
if (isInner()) {
|
||||
return parentClass.getAliasPkg();
|
||||
}
|
||||
return alias == null ? getPackage() : alias.getPkg();
|
||||
}
|
||||
|
||||
public String getAliasShortName() {
|
||||
return alias == null ? getShortName() : alias.getShortName();
|
||||
}
|
||||
|
||||
public String getAliasFullName() {
|
||||
if (alias != null) {
|
||||
String aliasFullName = alias.getFullName();
|
||||
if (aliasFullName == null) {
|
||||
return makeAliasFullName();
|
||||
}
|
||||
return aliasFullName;
|
||||
}
|
||||
if (parentClass != null && parentClass.hasAlias()) {
|
||||
return makeAliasFullName();
|
||||
}
|
||||
return getFullName();
|
||||
}
|
||||
|
||||
public boolean hasAlias() {
|
||||
if (alias != null) {
|
||||
return true;
|
||||
}
|
||||
return parentClass != null && parentClass.hasAlias();
|
||||
}
|
||||
|
||||
private void splitAndApplyNames(RootNode root, ArgType type, boolean canBeInner) {
|
||||
String fullObjectName = type.getObject();
|
||||
String clsPkg;
|
||||
String clsName;
|
||||
int dot = fullObjectName.lastIndexOf('.');
|
||||
if (dot == -1) {
|
||||
pkg = "";
|
||||
clsPkg = "";
|
||||
clsName = fullObjectName;
|
||||
} else {
|
||||
pkg = fullObjectName.substring(0, dot);
|
||||
clsPkg = fullObjectName.substring(0, dot);
|
||||
clsName = fullObjectName.substring(dot + 1);
|
||||
}
|
||||
|
||||
int sep = clsName.lastIndexOf('$');
|
||||
if (canBeInner && sep > 0 && sep != clsName.length() - 1) {
|
||||
String parClsName = pkg + "." + clsName.substring(0, sep);
|
||||
if (pkg.isEmpty()) {
|
||||
String parClsName = clsPkg + '.' + clsName.substring(0, sep);
|
||||
if (clsPkg.isEmpty()) {
|
||||
parClsName = clsName.substring(0, sep);
|
||||
}
|
||||
|
||||
pkg = null;
|
||||
parentClass = fromName(root, parClsName);
|
||||
clsName = clsName.substring(sep + 1);
|
||||
} else {
|
||||
pkg = clsPkg;
|
||||
parentClass = null;
|
||||
}
|
||||
this.name = clsName;
|
||||
this.fullName = makeFullClsName(clsName, false);
|
||||
this.fullName = makeFullName();
|
||||
}
|
||||
|
||||
public String makeFullClsName(String shortName, boolean raw) {
|
||||
private static String makeFullClsName(String pkg, String shortName, ClassInfo parentClass, boolean alias, boolean raw) {
|
||||
if (parentClass != null) {
|
||||
String innerSep = raw ? "$" : ".";
|
||||
return parentClass.makeFullClsName(parentClass.getShortName(), raw) + innerSep + shortName;
|
||||
String parentFullName;
|
||||
if (alias) {
|
||||
parentFullName = raw ? parentClass.makeAliasRawFullName() : parentClass.getAliasFullName();
|
||||
} else {
|
||||
parentFullName = raw ? parentClass.makeRawFullName() : parentClass.getFullName();
|
||||
}
|
||||
return parentFullName + innerSep + shortName;
|
||||
}
|
||||
return pkg.isEmpty() ? shortName : pkg + "." + shortName;
|
||||
return pkg.isEmpty() ? shortName : pkg + '.' + shortName;
|
||||
}
|
||||
|
||||
public String getFullPath() {
|
||||
ClassInfo usedAlias = getAlias();
|
||||
return usedAlias.getPackage().replace('.', File.separatorChar)
|
||||
private String makeFullName() {
|
||||
return makeFullClsName(pkg, name, parentClass, false, false);
|
||||
}
|
||||
|
||||
public String makeRawFullName() {
|
||||
return makeFullClsName(pkg, name, parentClass, false, true);
|
||||
}
|
||||
|
||||
private String makeAliasFullName() {
|
||||
return makeFullClsName(getAliasPkg(), getAliasShortName(), parentClass, true, false);
|
||||
}
|
||||
|
||||
private String makeAliasRawFullName() {
|
||||
return makeFullClsName(pkg, name, parentClass, true, true);
|
||||
}
|
||||
|
||||
public String getAliasFullPath() {
|
||||
return getAliasPkg().replace('.', File.separatorChar)
|
||||
+ File.separatorChar
|
||||
+ usedAlias.getNameWithoutPackage().replace('.', '_');
|
||||
+ getAliasNameWithoutPackage().replace('.', '_');
|
||||
}
|
||||
|
||||
public String getFullName() {
|
||||
@@ -126,25 +196,33 @@ public final class ClassInfo {
|
||||
return name;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getPackage() {
|
||||
if (parentClass != null) {
|
||||
return parentClass.getPackage();
|
||||
}
|
||||
if (pkg == null) {
|
||||
throw new JadxRuntimeException("Package is null for not inner class");
|
||||
}
|
||||
return pkg;
|
||||
}
|
||||
|
||||
public boolean isDefaultPackage() {
|
||||
return pkg.isEmpty();
|
||||
return getPackage().isEmpty();
|
||||
}
|
||||
|
||||
public String getRawName() {
|
||||
return type.getObject();
|
||||
}
|
||||
|
||||
public String getNameWithoutPackage() {
|
||||
public String getAliasNameWithoutPackage() {
|
||||
if (parentClass == null) {
|
||||
return name;
|
||||
return getAliasShortName();
|
||||
}
|
||||
return parentClass.getNameWithoutPackage() + "." + name;
|
||||
return parentClass.getAliasNameWithoutPackage() + '.' + getAliasShortName();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ClassInfo getParentClass() {
|
||||
return parentClass;
|
||||
}
|
||||
@@ -162,7 +240,12 @@ public final class ClassInfo {
|
||||
}
|
||||
|
||||
public void notInner(RootNode root) {
|
||||
splitNames(root, false);
|
||||
this.parentClass = null;
|
||||
splitAndApplyNames(root, type, false);
|
||||
}
|
||||
|
||||
public void updateNames(RootNode root) {
|
||||
splitAndApplyNames(root, type, isInner());
|
||||
}
|
||||
|
||||
public ArgType getType() {
|
||||
@@ -171,7 +254,7 @@ public final class ClassInfo {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return fullName;
|
||||
return getFullName();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -190,4 +273,9 @@ public final class ClassInfo {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull ClassInfo o) {
|
||||
return getFullName().compareTo(o.getFullName());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,8 +15,8 @@ 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;
|
||||
import jadx.core.utils.ErrorsCounter;
|
||||
|
||||
public class ConstStorage {
|
||||
|
||||
@@ -101,16 +101,16 @@ public class ConstStorage {
|
||||
|
||||
@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;
|
||||
}
|
||||
DexNode dex = cls.dex();
|
||||
if (value instanceof Integer) {
|
||||
FieldNode rField = getResourceField((Integer) value, dex);
|
||||
if (rField != null) {
|
||||
return rField;
|
||||
}
|
||||
}
|
||||
boolean foundInGlobal = globalValues.contains(value);
|
||||
if (foundInGlobal && !searchGlobal) {
|
||||
return null;
|
||||
@@ -139,6 +139,31 @@ public class ConstStorage {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private FieldNode getResourceField(Integer value, DexNode dex) {
|
||||
String str = resourcesNames.get(value);
|
||||
if (str == null) {
|
||||
return null;
|
||||
}
|
||||
ClassNode appResClass = dex.root().getAppResClass();
|
||||
if (appResClass == null) {
|
||||
return null;
|
||||
}
|
||||
String[] parts = str.split("/", 2);
|
||||
if (parts.length != 2) {
|
||||
return null;
|
||||
}
|
||||
String typeName = parts[0];
|
||||
String fieldName = parts[1];
|
||||
for (ClassNode innerClass : appResClass.getInnerClasses()) {
|
||||
if (innerClass.getClassInfo().getShortName().equals(typeName)) {
|
||||
return innerClass.searchFieldByName(fieldName);
|
||||
}
|
||||
}
|
||||
ErrorsCounter.classWarn(appResClass, "Not found resource field with id: " + value + ", name: " + str.replace('/', '.'));
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public FieldNode getConstFieldByLiteralArg(ClassNode cls, LiteralArg arg) {
|
||||
PrimitiveType type = arg.getType().getPrimitiveType();
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package jadx.core.dex.info;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import com.android.dex.FieldId;
|
||||
|
||||
import jadx.core.codegen.TypeGen;
|
||||
@@ -53,14 +55,26 @@ public final class FieldInfo {
|
||||
this.alias = alias;
|
||||
}
|
||||
|
||||
public boolean hasAlias() {
|
||||
return !Objects.equals(name, alias);
|
||||
}
|
||||
|
||||
public String getFullId() {
|
||||
return declClass.getFullName() + "." + name + ":" + TypeGen.signature(type);
|
||||
return declClass.getFullName() + '.' + name + ':' + TypeGen.signature(type);
|
||||
}
|
||||
|
||||
public String getRawFullId() {
|
||||
return declClass.makeRawFullName() + '.' + name + ':' + TypeGen.signature(type);
|
||||
}
|
||||
|
||||
public boolean isRenamed() {
|
||||
return !name.equals(alias);
|
||||
}
|
||||
|
||||
public boolean equalsNameAndType(FieldInfo other) {
|
||||
return name.equals(other.name) && type.equals(other.type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
@@ -85,6 +99,6 @@ public final class FieldInfo {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return declClass + "." + name + " " + type;
|
||||
return declClass + "." + name + ' ' + type;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,21 @@ public final class MethodInfo {
|
||||
shortId = makeSignature(true);
|
||||
}
|
||||
|
||||
private MethodInfo(ClassInfo declClass, String name, List<ArgType> args, ArgType retType) {
|
||||
this.name = name;
|
||||
alias = name;
|
||||
aliasFromPreset = false;
|
||||
this.declClass = declClass;
|
||||
|
||||
this.args = args;
|
||||
this.retType = retType;
|
||||
shortId = makeSignature(true);
|
||||
}
|
||||
|
||||
public static MethodInfo externalMth(ClassInfo declClass, String name, List<ArgType> args, ArgType retType) {
|
||||
return new MethodInfo(declClass, name, args, retType);
|
||||
}
|
||||
|
||||
public static MethodInfo fromDex(DexNode dex, int mthIndex) {
|
||||
MethodInfo mth = dex.root().getInfoStorage().getMethod(dex, mthIndex);
|
||||
if (mth != null) {
|
||||
@@ -43,8 +58,12 @@ public final class MethodInfo {
|
||||
}
|
||||
|
||||
public String makeSignature(boolean includeRetType) {
|
||||
return makeSignature(false, includeRetType);
|
||||
}
|
||||
|
||||
public String makeSignature(boolean useAlias, boolean includeRetType) {
|
||||
StringBuilder signature = new StringBuilder();
|
||||
signature.append(name);
|
||||
signature.append(useAlias ? alias : name);
|
||||
signature.append('(');
|
||||
for (ArgType arg : args) {
|
||||
signature.append(TypeGen.signature(arg));
|
||||
@@ -61,11 +80,15 @@ public final class MethodInfo {
|
||||
}
|
||||
|
||||
public String getFullName() {
|
||||
return declClass.getFullName() + "." + name;
|
||||
return declClass.getFullName() + '.' + name;
|
||||
}
|
||||
|
||||
public String getFullId() {
|
||||
return declClass.getFullName() + "." + shortId;
|
||||
return declClass.getFullName() + '.' + shortId;
|
||||
}
|
||||
|
||||
public String getRawFullId() {
|
||||
return declClass.makeRawFullName() + '.' + shortId;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -107,7 +130,7 @@ public final class MethodInfo {
|
||||
this.alias = alias;
|
||||
}
|
||||
|
||||
public boolean isRenamed() {
|
||||
public boolean hasAlias() {
|
||||
return !name.equals(alias);
|
||||
}
|
||||
|
||||
@@ -143,8 +166,7 @@ public final class MethodInfo {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return declClass.getFullName() + "." + name
|
||||
+ "(" + Utils.listToString(args) + "):" + retType;
|
||||
return declClass.getFullName() + '.' + name
|
||||
+ '(' + Utils.listToString(args) + "):" + retType;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -76,9 +76,8 @@ public class ArithNode extends InsnNode {
|
||||
return InsnUtils.formatOffset(offset) + ": "
|
||||
+ InsnUtils.insnTypeToString(insnType)
|
||||
+ getResult() + " = "
|
||||
+ getArg(0) + " "
|
||||
+ op.getSymbol() + " "
|
||||
+ getArg(0) + ' '
|
||||
+ op.getSymbol() + ' '
|
||||
+ getArg(1);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -24,5 +24,4 @@ public enum ArithOp {
|
||||
public String getSymbol() {
|
||||
return this.symbol;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
package jadx.core.dex.instructions;
|
||||
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
|
||||
public interface CallMthInterface {
|
||||
|
||||
MethodInfo getCallMth();
|
||||
}
|
||||
@@ -35,6 +35,6 @@ public final class ConstClassNode extends InsnNode {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString() + " " + clsType;
|
||||
return super.toString() + ' ' + clsType;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,6 @@ public final class ConstStringNode extends InsnNode {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString() + " \"" + str + "\"";
|
||||
return super.toString() + " \"" + str + '"';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package jadx.core.dex.instructions;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import com.android.dx.io.instructions.FillArrayDataPayloadDecodedInstruction;
|
||||
@@ -9,43 +10,45 @@ import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.LiteralArg;
|
||||
import jadx.core.dex.instructions.args.PrimitiveType;
|
||||
import jadx.core.dex.nodes.DexNode;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
public final class FillArrayNode extends InsnNode {
|
||||
|
||||
private static final ArgType ONE_BYTE_TYPE = ArgType.unknown(PrimitiveType.BOOLEAN, PrimitiveType.BYTE);
|
||||
private static final ArgType TWO_BYTES_TYPE = ArgType.unknown(PrimitiveType.SHORT, PrimitiveType.CHAR);
|
||||
private static final ArgType FOUR_BYTES_TYPE = ArgType.unknown(PrimitiveType.INT, PrimitiveType.FLOAT);
|
||||
private static final ArgType EIGHT_BYTES_TYPE = ArgType.unknown(PrimitiveType.LONG, PrimitiveType.DOUBLE);
|
||||
|
||||
private final Object data;
|
||||
private final int size;
|
||||
private ArgType elemType;
|
||||
|
||||
public FillArrayNode(int resReg, FillArrayDataPayloadDecodedInstruction payload) {
|
||||
super(InsnType.FILL_ARRAY, 0);
|
||||
ArgType elType;
|
||||
switch (payload.getElementWidthUnit()) {
|
||||
case 1:
|
||||
elType = ArgType.unknown(PrimitiveType.BOOLEAN, PrimitiveType.BYTE);
|
||||
break;
|
||||
case 2:
|
||||
elType = ArgType.unknown(PrimitiveType.SHORT, PrimitiveType.CHAR);
|
||||
break;
|
||||
case 4:
|
||||
elType = ArgType.unknown(PrimitiveType.INT, PrimitiveType.FLOAT);
|
||||
break;
|
||||
case 8:
|
||||
elType = ArgType.unknown(PrimitiveType.LONG, PrimitiveType.DOUBLE);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new JadxRuntimeException("Unknown array element width: " + payload.getElementWidthUnit());
|
||||
}
|
||||
setResult(InsnArg.reg(resReg, ArgType.array(elType)));
|
||||
super(InsnType.FILL_ARRAY, 1);
|
||||
ArgType elType = getElementType(payload.getElementWidthUnit());
|
||||
addArg(InsnArg.reg(resReg, ArgType.array(elType)));
|
||||
|
||||
this.data = payload.getData();
|
||||
this.size = payload.getSize();
|
||||
this.elemType = elType;
|
||||
}
|
||||
|
||||
private static ArgType getElementType(short elementWidthUnit) {
|
||||
switch (elementWidthUnit) {
|
||||
case 1:
|
||||
return ONE_BYTE_TYPE;
|
||||
case 2:
|
||||
return TWO_BYTES_TYPE;
|
||||
case 4:
|
||||
return FOUR_BYTES_TYPE;
|
||||
case 8:
|
||||
return EIGHT_BYTES_TYPE;
|
||||
default:
|
||||
throw new JadxRuntimeException("Unknown array element width: " + elementWidthUnit);
|
||||
}
|
||||
}
|
||||
|
||||
public Object getData() {
|
||||
return data;
|
||||
}
|
||||
@@ -58,34 +61,27 @@ public final class FillArrayNode extends InsnNode {
|
||||
return elemType;
|
||||
}
|
||||
|
||||
public void mergeElementType(DexNode dex, ArgType foundElemType) {
|
||||
ArgType r = ArgType.merge(dex, elemType, foundElemType);
|
||||
if (r != null) {
|
||||
elemType = r;
|
||||
}
|
||||
}
|
||||
|
||||
public List<LiteralArg> getLiteralArgs() {
|
||||
public List<LiteralArg> getLiteralArgs(ArgType type) {
|
||||
List<LiteralArg> list = new ArrayList<>(size);
|
||||
Object array = data;
|
||||
if (array instanceof int[]) {
|
||||
for (int b : (int[]) array) {
|
||||
list.add(InsnArg.lit(b, elemType));
|
||||
list.add(InsnArg.lit(b, type));
|
||||
}
|
||||
} else if (array instanceof byte[]) {
|
||||
for (byte b : (byte[]) array) {
|
||||
list.add(InsnArg.lit(b, elemType));
|
||||
list.add(InsnArg.lit(b, type));
|
||||
}
|
||||
} else if (array instanceof short[]) {
|
||||
for (short b : (short[]) array) {
|
||||
list.add(InsnArg.lit(b, elemType));
|
||||
list.add(InsnArg.lit(b, type));
|
||||
}
|
||||
} else if (array instanceof long[]) {
|
||||
for (long b : (long[]) array) {
|
||||
list.add(InsnArg.lit(b, elemType));
|
||||
list.add(InsnArg.lit(b, type));
|
||||
}
|
||||
} else {
|
||||
throw new JadxRuntimeException("Unknown type: " + data.getClass() + ", expected: " + elemType);
|
||||
throw new JadxRuntimeException("Unknown type: " + data.getClass() + ", expected: " + type);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
@@ -101,4 +97,25 @@ public final class FillArrayNode extends InsnNode {
|
||||
FillArrayNode other = (FillArrayNode) obj;
|
||||
return elemType.equals(other.elemType) && data == other.data;
|
||||
}
|
||||
|
||||
public String dataToString() {
|
||||
if (data instanceof int[]) {
|
||||
return Arrays.toString((int[]) data);
|
||||
}
|
||||
if (data instanceof short[]) {
|
||||
return Arrays.toString((short[]) data);
|
||||
}
|
||||
if (data instanceof byte[]) {
|
||||
return Arrays.toString((byte[]) data);
|
||||
}
|
||||
if (data instanceof long[]) {
|
||||
return Arrays.toString((long[]) data);
|
||||
}
|
||||
return "?";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString() + ", data: " + dataToString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package jadx.core.dex.instructions;
|
||||
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.utils.InsnUtils;
|
||||
|
||||
public class GotoNode extends TargetInsnNode {
|
||||
@@ -20,15 +19,6 @@ public class GotoNode extends TargetInsnNode {
|
||||
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);
|
||||
|
||||
@@ -16,21 +16,21 @@ 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.BYTE, PrimitiveType.SHORT, PrimitiveType.CHAR);
|
||||
|
||||
protected IfOp op;
|
||||
|
||||
private BlockNode thenBlock;
|
||||
private BlockNode elseBlock;
|
||||
|
||||
public IfNode(DecodedInstruction insn, IfOp op) {
|
||||
this(op, insn.getTarget(),
|
||||
InsnArg.reg(insn, 0, ARG_TYPE),
|
||||
insn.getRegisterCount() == 1 ? InsnArg.lit(0, ARG_TYPE) : InsnArg.reg(insn, 1, ARG_TYPE));
|
||||
super(InsnType.IF, insn.getTarget(), 2);
|
||||
this.op = op;
|
||||
ArgType argType = narrowTypeByOp(op);
|
||||
addArg(InsnArg.reg(insn, 0, argType));
|
||||
if (insn.getRegisterCount() == 1) {
|
||||
addArg(InsnArg.lit(0, argType));
|
||||
} else {
|
||||
addArg(InsnArg.reg(insn, 1, argType));
|
||||
}
|
||||
}
|
||||
|
||||
public IfNode(IfOp op, int targetOffset, InsnArg arg1, InsnArg arg2) {
|
||||
@@ -40,6 +40,22 @@ public class IfNode extends GotoNode {
|
||||
addArg(arg2);
|
||||
}
|
||||
|
||||
// change default types priority
|
||||
private static final ArgType WIDE_TYPE = ArgType.unknown(
|
||||
PrimitiveType.INT, PrimitiveType.BOOLEAN,
|
||||
PrimitiveType.OBJECT, PrimitiveType.ARRAY,
|
||||
PrimitiveType.BYTE, PrimitiveType.SHORT, PrimitiveType.CHAR);
|
||||
|
||||
private static final ArgType NUMBERS_TYPE = ArgType.unknown(
|
||||
PrimitiveType.INT, PrimitiveType.BYTE, PrimitiveType.SHORT, PrimitiveType.CHAR);
|
||||
|
||||
private static ArgType narrowTypeByOp(IfOp op) {
|
||||
if (op == IfOp.EQ || op == IfOp.NE) {
|
||||
return WIDE_TYPE;
|
||||
}
|
||||
return NUMBERS_TYPE;
|
||||
}
|
||||
|
||||
public IfOp getOp() {
|
||||
return op;
|
||||
}
|
||||
@@ -111,7 +127,7 @@ public class IfNode extends GotoNode {
|
||||
public String toString() {
|
||||
return InsnUtils.formatOffset(offset) + ": "
|
||||
+ InsnUtils.insnTypeToString(insnType)
|
||||
+ getArg(0) + " " + op.getSymbol() + " " + getArg(1)
|
||||
+ getArg(0) + ' ' + op.getSymbol() + ' ' + getArg(1)
|
||||
+ " -> " + (thenBlock != null ? thenBlock : InsnUtils.formatOffset(target));
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user