Compare commits

...

99 Commits

Author SHA1 Message Date
Skylot 1a736aadf5 v0.7.1 2018-03-25 12:30:39 +03:00
Skylot 2cea653249 fix bintray upload 2018-03-18 16:38:55 +03:00
Skylot 1085238031 res: don't use system locale for number formating (#238) 2018-03-18 15:25:57 +03:00
Skylot 1356d91423 res: don't add colon for empty namespace (#231) 2018-03-18 15:25:57 +03:00
sergey-wowwow fd2dc14ede Update ZipSecurity.java
Changes max diff size to prevent valid resources filtration
2018-03-13 19:20:52 +03:00
Skylot f91c5d3647 res: skip padding on file end (#225) 2018-03-11 19:19:06 +03:00
Skylot 1f3aebf584 res: close tag before cdata (#231) 2018-03-11 17:09:17 +03:00
Skylot b39d79a0f9 remove coverity and downloads badges 2018-03-11 10:25:28 +03:00
Skylot c410914208 core: skip finally extract visitor on error 2018-03-04 19:18:53 +03:00
Skylot a046f1caec core: ignore dex loading errors (#233) 2018-02-28 21:55:19 +03:00
Skylot c25f918cc5 gui: fix some sonar warnings 2018-02-28 21:50:14 +03:00
Skylot 6fb1c8d3b9 gui: don't decode resources on file open 2018-02-17 23:23:03 +03:00
Skylot 6047a27c89 build: disable codecov pull request report 2018-02-13 16:07:16 +03:00
Skylot 8446d016e4 cli: update jcommander lib 2018-02-13 15:31:49 +03:00
Skylot ab040d36d5 update all dependencies (#229) 2018-02-13 14:58:27 +03:00
skylot a2781b5bd3 Merge pull request #221 from rgoggins/master
Move contribution section to separate file
2018-02-12 21:45:41 +03:00
skylot a7903f31ac Merge pull request #228 from jpstotz/master
Search dialog improvements
2018-02-12 21:42:33 +03:00
Jan Peter Stotz c134837ca6 pagination for search results. 2018-02-12 16:20:12 +01:00
Jan Peter Stotz f0a57e6714 case insensitive option for searches 2018-02-12 13:17:40 +01:00
Skylot d9b0365c9f core: fix binary xml parser (#211) 2018-02-11 23:18:21 +03:00
Skylot 948f9456f5 core: change jadx args api for easier processing and validation 2018-02-10 21:32:51 +03:00
Skylot 32f94b463f core: add code lines for while loop 2018-02-07 21:21:53 +03:00
Skylot 035506496e core: add endless loop test 2018-02-04 17:08:22 +03:00
Ryan Goggins 477222a395 Update README.md 2018-02-02 03:59:10 -05:00
Ryan Goggins 4bdfdfcb36 Create CONTRIBUTING.md 2018-02-02 03:58:41 -05:00
Skylot 3167cd0817 fix bintray upload 2018-01-28 00:12:55 +03:00
Skylot b52f35259b core: support 'not-int' and 'not-long' instructions 2018-01-28 00:04:27 +03:00
Skylot 20bf85b14d core: ignore bogus opcode decode (#214) 2018-01-28 00:03:59 +03:00
Skylot f02b99a1d0 fix some sonar warnings 2018-01-26 17:36:30 +03:00
Skylot 9132ef57f1 core: use late deletion for NOP instruction (#215) 2018-01-26 16:08:13 +03:00
skylot d42bf2d43c Merge pull request #212 from KpChuck/master
Don't throw a DecodeException if dex files aren't found but --no-src used
2018-01-21 11:30:42 +03:00
KpChuck 89042fbf4a Don't throw a DecodeException if dex files aren't found but --no-src is enabled 2018-01-20 21:12:04 +00:00
Skylot fc4dcd2db5 core: prevent some null crash on resource decoding 2018-01-18 23:21:36 +03:00
Skylot 4e07d80ebc cli: fix errors list sorting 2018-01-18 23:16:46 +03:00
Skylot c4a462d601 core: update dx to version 14, allow to decompile java 8 classes (new instructions not implemented yet) 2018-01-18 23:15:20 +03:00
Skylot 7fe46fb6f3 gui: highlight words on double click (#210) 2018-01-18 22:43:20 +03:00
Skylot 2cb94856fd build: setup bintray unstable upload 2018-01-17 11:02:31 +03:00
Skylot f53fc03c6c core: use dynamic check for filesystem case-sensitivity (#158) 2018-01-16 21:09:09 +03:00
Skylot 9278c51035 build: use commits count in gitlab ci version 2018-01-16 21:09:08 +03:00
sergey-wowwow ca9dc5f944 Update README.md 2018-01-16 21:05:59 +03:00
unknown f30cfb6166 Merge branch 'master' of https://github.com/skylot/jadx into issue_204 2018-01-16 20:58:22 +03:00
unknown 9614929f77 Fixes build issues 2018-01-16 20:54:52 +03:00
skylot 8e418d4414 Merge pull request #206 from mtdcr/master
Decode attributes which may contain either enums or values
2018-01-16 15:58:19 +03:00
Andreas Oberritter 5e81bd833b Decode attributes which may contain either enums or values, e.g. layout_width
android:layout_width="UNKNOWN_DATA_0x6401" becomes android:layout_width="100dp".
2018-01-16 12:56:21 +01:00
unknown cc2ae80e7b Issue #204 2018-01-15 19:35:30 +03:00
sergey-wowwow b921f6097d Update JadxCLIArgs.java 2018-01-14 21:47:37 +03:00
sergey-wowwow 9679ef893b Update README.md 2018-01-14 21:40:08 +03:00
sergey-wowwow e4fc3cebfd Update JadxArgs.java
Changes flags to true
2018-01-14 21:34:17 +03:00
sergey-wowwow 75135819cf Delete .DS_Store 2018-01-14 21:31:01 +03:00
sergey-wowwow 072b6cce36 Update README.md 2018-01-14 21:08:49 +03:00
Sergey Toshin 5d60f2cdf2 PR for issue #191 2018-01-14 21:04:28 +03:00
Sergey Toshin c476593925 Changes MAX_SIZE_DIFF in ZipSecurity, and adds extra logging 2018-01-14 20:14:11 +03:00
skylot 089467a419 Merge pull request #203 from dgorshkov/patch-1
Readme - GUI screenshot fix
2018-01-14 19:50:22 +03:00
dgorshkov ee68e04f84 GUI screenshot fix 2018-01-14 17:35:26 +01:00
binjia.zhou 9cd46e74be fix some xml generate issues 2018-01-14 12:15:00 +03:00
sergey-wowwow 5781220415 Delete .DS_Store 2018-01-13 20:14:11 +03:00
Sergey Toshin 965fd66e0f Adds more checks for file write ops 2018-01-03 19:50:16 +01:00
Sergey Toshin 7d3caa2875 Adds checks for resources, and logs detected attacks 2018-01-03 18:20:21 +01:00
Sergey Toshin 418546a659 Merge with master 2018-01-03 17:55:55 +01:00
Skylot d586c84b56 reformat code and fix sonar warnings 2018-01-02 21:26:43 +03:00
Skylot 7b9e5fe99f build: add sonarqube 2018-01-02 19:33:12 +03:00
Skylot 648f0edc79 build: add gitlab-ci 2018-01-02 18:54:41 +03:00
Skylot 4d9d0884c3 remove old version of dx.jar 2018-01-02 17:05:25 +03:00
Skylot 19c0bbb94c Merge jiqimaogou/jadx 2018-01-02 16:27:56 +03:00
Skylot c6995c2283 remove .DS_Store file 2018-01-02 15:59:45 +03:00
Skylot 49a263454c update gradle wrapper to version 4.4.1 2018-01-02 15:28:10 +03:00
Skylot 454519220f ui: don't run full decompilation on start 2018-01-02 15:24:20 +03:00
Skylot 118fa98ca9 gui: fix incorrect settings loading 2018-01-02 15:24:13 +03:00
Skylot 001fa639be core: fix some concurrency issues 2018-01-02 15:23:36 +03:00
Skylot 009749cf8b core: ignore errors in debug info parser (fix #176) 2018-01-02 15:22:49 +03:00
Skylot da94e7b1be core: update dx to 1.13 for support java 8, build for jdk 8 2018-01-02 15:22:20 +03:00
skylot ea346145f6 Merge pull request #194 from wuyongzheng/master
fixed a few bugs resulting program hang
2018-01-02 15:16:09 +03:00
skylot a01c379c95 Merge pull request #171 from daramos/deobfuscation_work
deobfuscation fixes
2018-01-02 15:15:34 +03:00
skylot c9b781d5e1 Merge pull request #119 from ITMonkeys/master
support for Android InstantRun Apk
2018-01-02 15:14:40 +03:00
skylot 0b49abf3f5 Merge pull request #115 from bigfool/master
add some code to enable jadx can decompile apk by file's type, not only by file's extension
2018-01-02 15:12:10 +03:00
skylot e5fe4b0a99 Merge pull request #169 from appetizerio/master
Increase heap limit to avoid most OOMs in Gradle
2018-01-02 15:08:57 +03:00
skylot 7474d305fb Merge pull request #114 from allight/master
Replace jadx-core/lib/dx-1.10.jar with recent AOSP dx.jar
2018-01-02 15:08:21 +03:00
skylot 4716929158 Merge pull request #108 from Tneciv/master
add translation of Chinese
2018-01-02 15:07:45 +03:00
Sergey Toshin 528ca09e8e Fixes for ZIP and XML processors 2017-12-31 01:51:25 +03:00
Wu Yongzheng 233054219f fix issue #184
please refer to https://github.com/skylot/jadx/issues/184
2017-12-21 16:04:12 +08:00
Wu Yongzheng 0e2c4d4af1 fix thread-safe bug
wrap WeakHashMap with Collections.synchronizedMap, because it can be used by many worker threads.
2017-12-21 16:00:11 +08:00
Wu Yongzheng f101e9a775 fix thread-safe bug
Change HashMap to ConcurrentHashMap, because it can be used by many worker threads.
2017-12-21 15:58:37 +08:00
Daniel Ramos bf3863d1bf Fixed issue where deobfuscated classes,methods,and fields were not being commented with their original name. 2017-04-30 21:23:19 -04:00
Daniel Ramos 94e9291c40 Fixed issue where postProcess may overwrite Deobfuscated method names.
The function will now check if the method name was aliased by Deobfuscation and if so it will use that name for all the overriden methods. Note: If the deobfuscation file contains two names for the same overriden method in two related Classes then the name that "wins" is non-deterministic.
2017-04-30 21:22:59 -04:00
Daniel Ramos 459d133b5d Fixed issue where renamed classes/methods referenced in a different dex file would not be renamed properly. Deobfuscation only modified the Class info for the InfoStorage of the Dex file the class belonged to. If a class in another Dex file referenced it, it would not know of the rename.
This commit moves InfoStorage to the RootNode. This allows all classes to know of each other regardless of the Dex file. A dexId field was added to the DexNode class to allow the the MethodInfo.fromDex function to continue to use method index to locate methods. The getMethod and putMethod functions in InfoStorage was modified to take a DexNode. The DexNode id is used to create a unique key used for the lookup into the methods HashMap.
2017-04-30 13:34:21 -04:00
Daniel Ramos 773fad66bb Fix issue where name generation on variables of deobfuscated objects were sometimes based off of the original class name, and not the alias. 2017-04-29 15:26:07 -04:00
Daniel Ramos e250c73109 Fix issue with inner classes and empty package name. 2017-04-29 15:18:41 -04:00
Mingyuan Xia 6870c05ffa increase cli heap usage as well 2017-04-10 13:13:04 +08:00
Mingyuan Xia 199581bf74 tune start script heap size for real-world apks 2017-04-10 12:56:05 +08:00
Skylot a9ae971602 build: remove gradle plugins, update gradle to 2.14.1 (#145) 2016-12-22 11:54:56 +03:00
chenzhong.cz fe03c85b97 ensure a zip file by file content. 2016-11-08 15:45:35 +08:00
chenzhong.cz c338652045 no limit to the package id 2016-10-31 11:24:33 +08:00
chenzhong.cz 1f5cdeb01b support arsc raw file view 2016-07-09 12:38:00 +08:00
袭建帅 e53a72c5f5 support for Android InstantRun Apk
we should consider the input file could contain only one single dex, multi-dex, or instantRun support dex for Android .apk files
2016-05-12 18:40:00 +08:00
Alex Light fc2690888e Replace the java 1.8 only dx.jar with one supporting java 1.7
Also removed the travis test for java 1.6.

Java 1.7 version of dx.jar compiled with plaform/libcore at commit
c3e562a and platform/dalvik at commit db9197b with
https://android-review.googlesource.com/#/c/221127/1 cherry-picked on
top of it.
2016-05-02 09:50:39 -07:00
齐振芳 b4472fd7d4 delete comments 2016-05-02 09:03:25 +08:00
Alex Light 796d02506a Replace jadx-core/lib/dx-1.10.jar with recent AOSP dx.jar
Recently support has been added to AOSP for generating and running
version 037 dex files. In order to load these we update the dx.jar
with a recent version built from AOSP.
2016-04-28 16:54:55 -07:00
齐振芳 467f729f06 add file type detect, jadx file by file's header, not only file's extension 2016-04-28 16:25:14 +08:00
Tneciv 050ec8b988 Create Messages_zh_CN.properties
add translation of Chinese
2016-04-05 18:35:52 +08:00
431 changed files with 4752 additions and 3232 deletions
+14
View File
@@ -0,0 +1,14 @@
coverage:
precision: 2
round: down
range: "50...100"
status:
project:
default: on
patch:
default: on
changes:
default: off
comment: false
+4
View File
@@ -10,13 +10,17 @@ out/
*.ipr
*.iws
**/.DS_Store
bin/
target/
build/
classes/
idea/
.gradle/
gradle.properties
jadx-output/
*-tmp/
*.dex
+23
View File
@@ -0,0 +1,23 @@
image: java:8
variables:
GRADLE_OPTS: "-Dorg.gradle.daemon=false"
TERM: "dumb"
before_script:
- chmod +x gradlew
stages:
- build
build:
stage: build
script:
- sed -i " 1 s/.*/&-b$(git rev-list --count HEAD)-$(git rev-parse --short HEAD)/" version
- cat version
- ./gradlew -g /cache/.gradle clean build jacocoTestReport
- ./gradlew -g /cache/.gradle clean sonarqube -Dsonar.host.url=$SONAR_HOST -Dsonar.organization=$SONAR_ORG -Dsonar.login=$SONAR_TOKEN
- ./gradlew -g /cache/.gradle clean dist
artifacts:
paths:
- build/jadx*.zip
+23 -12
View File
@@ -1,28 +1,39 @@
language: java
jdk:
- oraclejdk8
- oraclejdk7
- openjdk6
env:
- TERM=dumb
before_install:
- chmod +x gradlew
script:
- TERM=dumb ./gradlew clean build dist
- sed -i " 1 s/.*/&-b$TRAVIS_BUILD_NUMBER-$(git rev-parse --short HEAD)/" version
- cat version
- sed -i "s/BUILD_VERSION/$(head -c -1 version)/g" bintray.json
- ./gradlew clean build
after_success:
- TERM=dumb ./gradlew jacocoTestReport coveralls
- ./gradlew clean build jacocoTestReport
- bash <(curl -s https://codecov.io/bash) || echo "Codecov did not collect coverage reports"
- ./gradlew clean sonarqube -Dsonar.host.url=$SONAR_HOST -Dsonar.organization=$SONAR_ORG -Dsonar.login=$SONAR_TOKEN
- ./gradlew clean dist
deploy:
provider: bintray
edge:
branch: v1.8.47
file: bintray.json
user: skylot
key: $BINTRAY_KEY
skip_cleanup: true
on:
branch: master
tags: false
sudo: false
cache:
directories:
- $HOME/.gradle/caches/
- $HOME/.gradle/wrapper/
before_cache:
- rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
notifications:
email:
- skylot@gmail.com
+7
View File
@@ -0,0 +1,7 @@
# Contribution
To support this project you can:
- Post thoughts about new features/optimizations that important to you
- Submit bug using one of following patterns:
* Java code examples which decompiles incorrectly
* Error log and link to _public available_ apk file or app page on Google play
+40 -41
View File
@@ -1,24 +1,33 @@
## JADX
[![Build Status](https://travis-ci.org/skylot/jadx.png?branch=master)](https://travis-ci.org/skylot/jadx)
[![Build Status](https://drone.io/github.com/skylot/jadx/status.png)](https://drone.io/github.com/skylot/jadx/latest)
[![Coverage Status](https://coveralls.io/repos/skylot/jadx/badge.png)](https://coveralls.io/r/skylot/jadx)
[![Coverity Scan Build Status](https://scan.coverity.com/projects/2166/badge.svg)](https://scan.coverity.com/projects/2166)
[![Code Coverage](https://codecov.io/gh/skylot/jadx/branch/master/graph/badge.svg)](https://codecov.io/gh/skylot/jadx)
[![SonarQube Bugs](https://sonarcloud.io/api/badges/measure?key=jadx&metric=bugs)](https://sonarcloud.io/dashboard?id=jadx)
[![License](http://img.shields.io/:license-apache-blue.svg)](http://www.apache.org/licenses/LICENSE-2.0.html)
**jadx** - Dex to Java decompiler
Command line and GUI tools for produce Java source code from Android Dex and Apk files
![jadx-gui screenshot](http://skylot.github.io/jadx/jadx-gui.png)
![jadx-gui screenshot](https://i.imgur.com/h917IBZ.png)
### Downloads
- [unstable](https://drone.io/github.com/skylot/jadx/files)
- from [github](https://github.com/skylot/jadx/releases)
- from [sourceforge](http://sourceforge.net/projects/jadx/files/)
- latest [unstable build: ![Download](https://api.bintray.com/packages/skylot/jadx/unstable/images/download.svg) ](https://bintray.com/skylot/jadx/unstable/_latestVersion#files)
- release from [github: ![Latest release](https://img.shields.io/github/release/skylot/jadx.svg)](https://github.com/skylot/jadx/releases/latest)
- release from [bintray: ![Download](https://api.bintray.com/packages/skylot/jadx/releases/images/download.svg) ](https://bintray.com/skylot/jadx/releases/_latestVersion#files)
- release from [sourceforge](http://sourceforge.net/projects/jadx/files/)
After download unpack zip file go to `bin` directory and run:
- `jadx` - command line version
- `jadx-gui` - graphical version
On Windows run `.bat` files with double-click\
**Note:** ensure you have installed Java 8 64-bit version
### Building from source
Java 8 JDK or higher must be installed:
git clone https://github.com/skylot/jadx.git
cd jadx
./gradlew dist
@@ -34,7 +43,7 @@ Run **jadx** on itself:
cd build/jadx/
bin/jadx -d out lib/jadx-core-*.jar
#or
# or
bin/jadx-gui lib/jadx-core-*.jar
@@ -42,27 +51,31 @@ Run **jadx** on itself:
```
jadx[-gui] [options] <input file> (.dex, .apk, .jar or .class)
options:
-d, --output-dir - output directory
-j, --threads-count - processing threads count
-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-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
-e, --export-gradle - save as android gradle project
--show-bad-code - show inconsistent code (incorrectly decompiled)
--no-imports - disable use of imports, always write entire package name
--no-replace-consts - don't replace constant value with matching constant field
--escape-unicode - escape non latin characters in strings (with \u)
--deobf - activate deobfuscation
--deobf-min - min length of name
--deobf-max - max length of name
--deobf-rewrite-cfg - force to save deobfuscation map
--deobf-use-sourcename - use source file name as class name alias
--cfg - save methods control flow graph to dot file
--raw-cfg - save methods control flow graph (use raw instructions)
-f, --fallback - make simple dump (using goto instead of 'if', 'for', etc)
-v, --verbose - verbose output
-h, --help - print this help
Example:
jadx -d out classes.dex
```
These options also worked on jadx-gui running from command line and override options from preferences dialog
### Troubleshooting
##### Out of memory error:
@@ -73,21 +86,7 @@ Example:
* edit 'jadx' script (jadx.bat on Windows) and setup bigger heap size:
`DEFAULT_JVM_OPTS="-Xmx2500M"`
### Contribution
To support this project you can:
- Post thoughts about new features/optimizations that important to you
- Submit bug using one of following patterns:
* Java code examples which decompiles incorrectly
* Error log and link to _public available_ apk file or app page on Google play
And any other comments will be very helpfull,
because at current stage of development it is very time consuming
to **find** new bugs, design and implement new features.
Also I need to **prioritize** these task for complete most important at first.
---------------------------------------
*Licensed under the Apache 2.0 License*
*Copyright 2015 by Skylot*
*Copyright 2018 by Skylot*
+34
View File
@@ -0,0 +1,34 @@
{
"package": {
"name": "unstable",
"repo": "jadx",
"subject": "skylot",
"desc": "Unstable",
"website_url": "https://github.com/skylot/jadx",
"issue_tracker_url": "https://github.com/skylot/jadx/issues",
"vcs_url": "https://github.com/skylot/jadx.git",
"licenses": [
"Apache-2.0"
],
"labels": [
"unstable"
],
"public_download_numbers": true,
"public_stats": true
},
"version": {
"name": "BUILD_VERSION",
"desc": "Dev build: BUILD_VERSION"
},
"files": [
{
"includePattern": "build/(jadx.*\.zip)",
"uploadPattern": "$1",
"matrixParams": {
"override": 1
},
"list_in_downloads": true
}
],
"publish": true
}
+41 -27
View File
@@ -1,36 +1,33 @@
buildscript {
repositories {
mavenCentral()
jcenter()
}
}
plugins {
id "com.github.kt3k.coveralls" version "2.3.1"
id "info.solidsoft.pitest" version "1.1.4"
// id "com.github.ben-manes.versions" version "0.8"
id 'com.github.ksoichiro.console.reporter' version '0.5.0'
id 'org.sonarqube' version '2.6.2'
id 'com.github.ben-manes.versions' version '0.17.0'
}
ext.jadxVersion = file('version').readLines().get(0)
version = jadxVersion
subprojects {
allprojects {
apply plugin: 'java'
apply plugin: 'groovy'
apply plugin: 'jacoco'
apply plugin: 'com.github.kt3k.coveralls'
apply plugin: 'com.github.ksoichiro.console.reporter'
version = jadxVersion
tasks.withType(JavaCompile) {
sourceCompatibility = JavaVersion.VERSION_1_6
targetCompatibility = JavaVersion.VERSION_1_6
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
if (!"$it".contains(':jadx-samples:')) {
options.compilerArgs << '-Xlint' << '-Xlint:unchecked' << '-Xlint:deprecation'
}
}
compileJava {
options.encoding = "UTF-8"
}
jar {
version = jadxVersion
manifest {
@@ -39,30 +36,53 @@ subprojects {
}
dependencies {
compile 'org.slf4j:slf4j-api:1.7.10'
compile 'org.slf4j:slf4j-api:1.7.25'
testCompile 'ch.qos.logback:logback-classic:1.1.2'
testCompile 'ch.qos.logback:logback-classic:1.2.3'
testCompile 'junit:junit:4.12'
testCompile 'org.hamcrest:hamcrest-library:1.3'
testCompile 'org.mockito:mockito-core:1.10.19'
testCompile 'org.spockframework:spock-core:1.0-groovy-2.4'
testCompile 'cglib:cglib-nodep:3.1'
testCompile 'org.mockito:mockito-core:2.15.0'
testCompile 'org.spockframework:spock-core:1.1-groovy-2.4'
testCompile 'cglib:cglib-nodep:3.2.6'
}
repositories {
mavenCentral()
mavenLocal()
mavenCentral()
jcenter()
}
jacoco {
toolVersion = "0.8.0"
}
jacocoTestReport {
reports {
xml.enabled = true // coveralls plugin depends on xml format report
xml.enabled = true
html.enabled = true
}
}
}
sonarqube {
properties {
property 'sonar.exclusions', '**/jadx/samples/**/*,**/test-app/**/*'
property 'sonar.coverage.exclusions', '**/jadx/gui/**/*'
}
}
dependencyUpdates.resolutionStrategy = {
componentSelection { rules ->
rules.all { ComponentSelection selection ->
boolean rejected = ['alpha', 'beta', 'rc', 'cr', 'm', 'atlassian'].any { qualifier ->
selection.candidate.version ==~ /(?i).*[.-]${qualifier}[.\d-]*/
}
if (rejected) {
selection.reject('Release candidate')
}
}
}
}
task copyArtifacts(type: Sync, dependsOn: ['jadx-cli:installDist', 'jadx-gui:installDist']) {
destinationDir file("$buildDir/jadx")
['jadx-cli', 'jadx-gui'].each {
@@ -86,9 +106,6 @@ task samples(dependsOn: 'jadx-samples:samples') {
task testAppCheck(dependsOn: 'jadx-test-app:testAppCheck') {
}
task pitest(overwrite: true, dependsOn: 'jadx-core:pitest') {
}
task cleanBuildDir(type: Delete) {
delete buildDir
}
@@ -97,6 +114,3 @@ build.dependsOn(dist, samples)
clean.dependsOn(cleanBuildDir)
task wrapper(type: Wrapper) {
gradleVersion = '2.7'
}
Binary file not shown.
+1 -1
View File
@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.7-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-4.5.1-bin.zip
Vendored
+59 -51
View File
@@ -1,4 +1,4 @@
#!/usr/bin/env bash
#!/usr/bin/env sh
##############################################################################
##
@@ -6,47 +6,6 @@
##
##############################################################################
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn ( ) {
echo "$*"
}
die ( ) {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
esac
# For Cygwin, ensure paths are in UNIX format before anything is touched.
if $cygwin ; then
[ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
fi
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
@@ -61,9 +20,49 @@ while [ -h "$PRG" ] ; do
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >&-
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >&-
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
@@ -90,7 +89,7 @@ location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
@@ -114,6 +113,7 @@ fi
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
@@ -154,11 +154,19 @@ if $cygwin ; then
esac
fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("$@")
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
APP_ARGS=$(save "$@")
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"
Vendored
+4 -10
View File
@@ -8,14 +8,14 @@
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
@@ -46,10 +46,9 @@ echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windowz variants
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args
@rem Slurp the command line arguments.
@@ -60,11 +59,6 @@ set _SKIP=2
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
goto execute
:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$
:execute
@rem Setup the command line
+5 -2
View File
@@ -5,8 +5,8 @@ applicationName = 'jadx'
dependencies {
compile(project(':jadx-core'))
compile 'com.beust:jcommander:1.47'
compile 'ch.qos.logback:logback-classic:1.1.2'
compile 'com.beust:jcommander:1.72'
compile 'ch.qos.logback:logback-classic:1.2.3'
}
applicationDistribution.with {
@@ -18,3 +18,6 @@ applicationDistribution.with {
}
}
startScripts {
defaultJvmOpts = [ '-Xms2g', '-Xmx4g' ]
}
+14 -42
View File
@@ -1,32 +1,34 @@
package jadx.cli;
import jadx.api.JadxDecompiler;
import jadx.core.utils.exceptions.JadxException;
import java.io.File;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.JadxDecompiler;
import jadx.core.utils.exceptions.JadxArgsValidateException;
public class JadxCLI {
private static final Logger LOG = LoggerFactory.getLogger(JadxCLI.class);
public static void main(String[] args) throws JadxException {
public static void main(String[] args) {
try {
JadxCLIArgs jadxArgs = new JadxCLIArgs();
if (processArgs(jadxArgs, args)) {
if (jadxArgs.processArgs(args)) {
processAndSave(jadxArgs);
}
} catch (Throwable e) {
} catch (Exception e) {
LOG.error("jadx error: {}", e.getMessage(), e);
System.exit(1);
}
}
static void processAndSave(JadxCLIArgs jadxArgs) throws JadxException {
JadxDecompiler jadx = new JadxDecompiler(jadxArgs);
jadx.setOutputDir(jadxArgs.getOutDir());
jadx.loadFiles(jadxArgs.getInput());
static void processAndSave(JadxCLIArgs inputArgs) {
JadxDecompiler jadx = new JadxDecompiler(inputArgs.toJadxArgs());
try {
jadx.load();
} catch (JadxArgsValidateException e) {
LOG.error("Incorrect arguments: {}", e.getMessage());
System.exit(1);
}
jadx.save();
if (jadx.getErrorsCount() != 0) {
jadx.printErrorsReport();
@@ -35,34 +37,4 @@ public class JadxCLI {
LOG.info("done");
}
}
static boolean processArgs(JadxCLIArgs jadxArgs, String[] args) throws JadxException {
if (!jadxArgs.processArgs(args)) {
return false;
}
if (jadxArgs.getInput().isEmpty()) {
LOG.error("Please specify input file");
jadxArgs.printUsage();
return false;
}
File outputDir = jadxArgs.getOutDir();
if (outputDir == null) {
String outDirName;
File file = jadxArgs.getInput().get(0);
String name = file.getName();
int pos = name.lastIndexOf('.');
if (pos != -1) {
outDirName = name.substring(0, pos);
} else {
outDirName = name + "-jadx-out";
}
LOG.info("output directory: {}", outDirName);
outputDir = new File(outDirName);
jadxArgs.setOutputDir(outputDir);
}
if (outputDir.exists() && !outputDir.isDirectory()) {
throw new JadxException("Output directory exists as file " + outputDir);
}
return true;
}
}
@@ -1,38 +1,40 @@
package jadx.cli;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.Appender;
import jadx.api.IJadxArgs;
import jadx.api.JadxDecompiler;
import jadx.core.utils.exceptions.JadxException;
import java.io.File;
import java.io.PrintStream;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.beust.jcommander.IStringConverter;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.Appender;
import com.beust.jcommander.JCommander;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.ParameterDescription;
import com.beust.jcommander.ParameterException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class JadxCLIArgs implements IJadxArgs {
import jadx.api.JadxArgs;
import jadx.api.JadxDecompiler;
import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.files.FileUtils;
@Parameter(description = "<input file> (.dex, .apk, .jar or .class)")
protected List<String> files;
public class JadxCLIArgs {
@Parameter(description = "<input file> (.apk, .dex, .jar or .class)")
protected List<String> files = new ArrayList<>(1);
@Parameter(names = {"-d", "--output-dir"}, description = "output directory")
protected String outDirName;
protected String outDir;
@Parameter(names = {"-j", "--threads-count"}, description = "processing threads count")
protected int threadsCount = Math.max(1, Runtime.getRuntime().availableProcessors() / 2);
@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")
protected String outDirRes;
@Parameter(names = {"-r", "--no-res"}, description = "do not decode resources")
protected boolean skipResources = false;
@@ -43,11 +45,16 @@ public class JadxCLIArgs implements IJadxArgs {
@Parameter(names = {"-e", "--export-gradle"}, description = "save as android gradle project")
protected boolean exportAsGradleProject = false;
@Parameter(names = {"-j", "--threads-count"}, description = "processing threads count")
protected int threadsCount = JadxArgs.DEFAULT_THREADS_COUNT;
@Parameter(names = {"--show-bad-code"}, description = "show inconsistent code (incorrectly decompiled)")
protected boolean showInconsistentCode = false;
@Parameter(names = "--no-replace-consts", converter = InvertedBooleanConverter.class,
description = "don't replace constant value with matching constant field")
@Parameter(names = {"--no-imports"}, description = "disable use of imports, always write entire package name")
protected boolean useImports = true;
@Parameter(names = "--no-replace-consts", description = "don't replace constant value with matching constant field")
protected boolean replaceConsts = true;
@Parameter(names = {"--escape-unicode"}, description = "escape non latin characters in strings (with \\u)")
@@ -80,19 +87,19 @@ public class JadxCLIArgs implements IJadxArgs {
@Parameter(names = {"-v", "--verbose"}, description = "verbose output")
protected boolean verbose = false;
@Parameter(names = {"--version"}, description = "print jadx version")
protected boolean printVersion = false;
@Parameter(names = {"-h", "--help"}, description = "print this help", help = true)
protected boolean printHelp = false;
private final List<File> input = new ArrayList<File>(1);
private File outputDir;
public boolean processArgs(String[] args) {
return parse(args) && process();
}
private boolean parse(String[] args) {
try {
new JCommander(this, args);
makeJCommander().parse(args);
return true;
} catch (ParameterException e) {
System.err.println("Arguments parse error: " + e.getMessage());
@@ -101,32 +108,24 @@ public class JadxCLIArgs implements IJadxArgs {
}
}
private JCommander makeJCommander() {
return JCommander.newBuilder().addObject(this).build();
}
private boolean process() {
if (isPrintHelp()) {
if (printHelp) {
printUsage();
return false;
}
if (printVersion) {
System.out.println(JadxDecompiler.getVersion());
return false;
}
try {
if (threadsCount <= 0) {
throw new JadxException("Threads count must be positive");
throw new JadxException("Threads count must be positive, got: " + threadsCount);
}
if (files != null) {
for (String fileName : files) {
File file = new File(fileName);
if (file.exists()) {
input.add(file);
} else {
throw new JadxException("File not found: " + file);
}
}
}
if (input.size() > 1) {
throw new JadxException("Only one input file is supported");
}
if (outDirName != null) {
outputDir = new File(outDirName);
}
if (isVerbose()) {
if (verbose) {
ch.qos.logback.classic.Logger rootLogger =
(ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
// remove INFO ThresholdFilter
@@ -144,7 +143,7 @@ public class JadxCLIArgs implements IJadxArgs {
}
public void printUsage() {
JCommander jc = new JCommander(this);
JCommander jc = makeJCommander();
// print usage in not sorted fields order (by default its sorted by description)
PrintStream out = System.out;
out.println();
@@ -154,7 +153,7 @@ public class JadxCLIArgs implements IJadxArgs {
out.println("options:");
List<ParameterDescription> params = jc.getParameters();
Map<String, ParameterDescription> paramsMap = new LinkedHashMap<String, ParameterDescription>(params.size());
Map<String, ParameterDescription> paramsMap = new LinkedHashMap<>(params.size());
int maxNamesLen = 0;
for (ParameterDescription p : params) {
paramsMap.put(p.getParameterized().getName(), p);
@@ -171,13 +170,13 @@ public class JadxCLIArgs implements IJadxArgs {
continue;
}
StringBuilder opt = new StringBuilder();
opt.append(' ').append(p.getNames());
opt.append(" ").append(p.getNames());
addSpaces(opt, maxNamesLen - opt.length() + 2);
opt.append("- ").append(p.getDescription());
out.println(opt);
}
out.println("Example:");
out.println(" jadx -d out classes.dex");
out.println(" jadx -d out classes.dex");
}
private static void addSpaces(StringBuilder str, int count) {
@@ -186,106 +185,111 @@ public class JadxCLIArgs implements IJadxArgs {
}
}
public static class InvertedBooleanConverter implements IStringConverter<Boolean> {
@Override
public Boolean convert(String value) {
return "false".equals(value);
}
public JadxArgs toJadxArgs() {
JadxArgs args = new JadxArgs();
args.setInputFiles(files.stream().map(FileUtils::toFile).collect(Collectors.toList()));
args.setOutDir(FileUtils.toFile(outDir));
args.setOutDirSrc(FileUtils.toFile(outDirSrc));
args.setOutDirRes(FileUtils.toFile(outDirRes));
args.setThreadsCount(threadsCount);
args.setSkipSources(skipSources);
args.setSkipResources(skipResources);
args.setFallbackMode(fallbackMode);
args.setShowInconsistentCode(showInconsistentCode);
args.setCfgOutput(cfgOutput);
args.setRawCFGOutput(rawCfgOutput);
args.setReplaceConsts(replaceConsts);
args.setDeobfuscationOn(deobfuscationOn);
args.setDeobfuscationForceSave(deobfuscationForceSave);
args.setDeobfuscationMinLength(deobfuscationMinLength);
args.setDeobfuscationMaxLength(deobfuscationMaxLength);
args.setUseSourceNameAsClassAlias(deobfuscationUseSourceNameAsAlias);
args.setEscapeUnicode(escapeUnicode);
args.setExportAsGradleProject(exportAsGradleProject);
args.setUseImports(useImports);
return args;
}
public List<File> getInput() {
return input;
public List<String> getFiles() {
return files;
}
@Override
public File getOutDir() {
return outputDir;
public String getOutDir() {
return outDir;
}
public void setOutputDir(File outputDir) {
this.outputDir = outputDir;
public String getOutDirSrc() {
return outDirSrc;
}
public boolean isPrintHelp() {
return printHelp;
public String getOutDirRes() {
return outDirRes;
}
@Override
public boolean isSkipResources() {
return skipResources;
}
@Override
public boolean isSkipSources() {
return skipSources;
}
@Override
public int getThreadsCount() {
return threadsCount;
}
@Override
public boolean isCFGOutput() {
return cfgOutput;
}
@Override
public boolean isRawCFGOutput() {
return rawCfgOutput;
}
@Override
public boolean isFallbackMode() {
return fallbackMode;
}
@Override
public boolean isShowInconsistentCode() {
return showInconsistentCode;
}
@Override
public boolean isVerbose() {
return verbose;
public boolean isUseImports() {
return useImports;
}
@Override
public boolean isDeobfuscationOn() {
return deobfuscationOn;
}
@Override
public int getDeobfuscationMinLength() {
return deobfuscationMinLength;
}
@Override
public int getDeobfuscationMaxLength() {
return deobfuscationMaxLength;
}
@Override
public boolean isDeobfuscationForceSave() {
return deobfuscationForceSave;
}
@Override
public boolean useSourceNameAsClassAlias() {
public boolean isDeobfuscationUseSourceNameAsAlias() {
return deobfuscationUseSourceNameAsAlias;
}
@Override
public boolean escapeUnicode() {
return escapeUnicode;
}
@Override
public boolean isEscapeUnicode() {
return escapeUnicode;
}
public boolean isCfgOutput() {
return cfgOutput;
}
public boolean isRawCfgOutput() {
return rawCfgOutput;
}
public boolean isReplaceConsts() {
return replaceConsts;
}
@Override
public boolean isExportAsGradleProject() {
return exportAsGradleProject;
}
+1 -1
View File
@@ -5,7 +5,7 @@
<level>INFO</level>
</filter>
<encoder>
<pattern>%d{HH:mm:ss} %-5level - %msg%n</pattern>
<pattern>%-5level - %msg%n</pattern>
</encoder>
</appender>
@@ -1,22 +1,40 @@
package jadx.cli;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
public class JadxCLIArgsTest {
private static final Logger LOG = LoggerFactory.getLogger(JadxCLIArgsTest.class);
@Test
public void testInvertedBooleanOption() throws Exception {
public void testInvertedBooleanOption() {
assertThat(parse("--no-replace-consts").isReplaceConsts(), is(false));
assertThat(parse("").isReplaceConsts(), is(true));
}
@Test
public void testEscapeUnicodeOption() {
assertThat(parse("--escape-unicode").isEscapeUnicode(), is(true));
assertThat(parse("").isEscapeUnicode(), is(false));
}
@Test
public void testSrcOption() {
assertThat(parse("--no-src").isSkipSources(), is(true));
assertThat(parse("-s").isSkipSources(), is(true));
assertThat(parse("").isSkipSources(), is(false));
}
private JadxCLIArgs parse(String... args) {
JadxCLIArgs jadxArgs = new JadxCLIArgs();
boolean res = jadxArgs.processArgs(args);
assertThat(res, is(true));
LOG.info("Jadx args: {}", jadxArgs.toJadxArgs());
return jadxArgs;
}
}
+7 -20
View File
@@ -1,28 +1,15 @@
ext.jadxClasspath = 'clsp-data/android-5.1.jar'
apply plugin: "info.solidsoft.pitest"
dependencies {
runtime files(jadxClasspath)
compile files('lib/dx-1.10.jar')
compile 'commons-io:commons-io:2.4'
compile 'org.ow2.asm:asm:5.0.3'
compile 'com.intellij:annotations:12.0'
compile 'uk.com.robust-it:cloning:1.9.2'
compile files('lib/dx-1.14.jar')
compile 'commons-io:commons-io:2.6'
compile 'org.ow2.asm:asm:6.0'
compile 'org.jetbrains:annotations:15.0'
compile 'uk.com.robust-it:cloning:1.9.9'
testCompile 'org.smali:smali:2.0.3'
testCompile 'org.smali:smali:2.2.2'
testCompile 'org.smali:baksmali:2.2.2'
}
task packTests(type: Jar) {
classifier = 'tests'
from sourceSets.test.output
}
pitest {
excludedMethods = ['toString']
threads = 4
enableDefaultIncrementalAnalysis = true
outputFormats = ['XML', 'HTML']
jvmArgs = ['-Xmx12G']
}
Binary file not shown.
Binary file not shown.
@@ -1,45 +0,0 @@
package jadx.api;
import java.io.File;
public interface IJadxArgs {
File getOutDir();
int getThreadsCount();
boolean isCFGOutput();
boolean isRawCFGOutput();
boolean isFallbackMode();
boolean isShowInconsistentCode();
boolean isVerbose();
boolean isSkipResources();
boolean isSkipSources();
boolean isDeobfuscationOn();
int getDeobfuscationMinLength();
int getDeobfuscationMaxLength();
boolean isDeobfuscationForceSave();
boolean useSourceNameAsClassAlias();
boolean escapeUnicode();
/**
* Replace constant values with static final fields with same value
*/
boolean isReplaceConsts();
/**
* Save as gradle project
*/
boolean isExportAsGradleProject();
}
+86 -28
View File
@@ -1,19 +1,33 @@
package jadx.api;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
public class JadxArgs implements IJadxArgs {
public class JadxArgs {
private File outDir = new File("jadx-output");
private int threadsCount = Math.max(1, Runtime.getRuntime().availableProcessors() - 1);
public static final int DEFAULT_THREADS_COUNT = Math.max(1, Runtime.getRuntime().availableProcessors() / 2);
public static final String DEFAULT_OUT_DIR = "jadx-output";
public static final String DEFAULT_SRC_DIR = "sources";
public static final String DEFAULT_RES_DIR = "resources";
private List<File> inputFiles = new ArrayList<>(1);
private File outDir;
private File outDirSrc;
private File outDirRes;
private int threadsCount = DEFAULT_THREADS_COUNT;
private boolean cfgOutput = false;
private boolean rawCFGOutput = false;
private boolean isVerbose = false;
private boolean fallbackMode = false;
private boolean showInconsistentCode = false;
private boolean useImports = true;
private boolean isSkipResources = false;
private boolean isSkipSources = false;
@@ -28,7 +42,24 @@ public class JadxArgs implements IJadxArgs {
private boolean replaceConsts = true;
private boolean exportAsGradleProject = false;
@Override
public JadxArgs() {
// use default options
}
public void setRootDir(File rootDir) {
setOutDir(rootDir);
setOutDirSrc(new File(rootDir, DEFAULT_SRC_DIR));
setOutDirRes(new File(rootDir, DEFAULT_RES_DIR));
}
public List<File> getInputFiles() {
return inputFiles;
}
public void setInputFiles(List<File> inputFiles) {
this.inputFiles = inputFiles;
}
public File getOutDir() {
return outDir;
}
@@ -37,7 +68,22 @@ public class JadxArgs implements IJadxArgs {
this.outDir = outDir;
}
@Override
public File getOutDirSrc() {
return outDirSrc;
}
public void setOutDirSrc(File outDirSrc) {
this.outDirSrc = outDirSrc;
}
public File getOutDirRes() {
return outDirRes;
}
public void setOutDirRes(File outDirRes) {
this.outDirRes = outDirRes;
}
public int getThreadsCount() {
return threadsCount;
}
@@ -46,8 +92,7 @@ public class JadxArgs implements IJadxArgs {
this.threadsCount = threadsCount;
}
@Override
public boolean isCFGOutput() {
public boolean isCfgOutput() {
return cfgOutput;
}
@@ -55,7 +100,6 @@ public class JadxArgs implements IJadxArgs {
this.cfgOutput = cfgOutput;
}
@Override
public boolean isRawCFGOutput() {
return rawCFGOutput;
}
@@ -64,7 +108,6 @@ public class JadxArgs implements IJadxArgs {
this.rawCFGOutput = rawCFGOutput;
}
@Override
public boolean isFallbackMode() {
return fallbackMode;
}
@@ -73,7 +116,6 @@ public class JadxArgs implements IJadxArgs {
this.fallbackMode = fallbackMode;
}
@Override
public boolean isShowInconsistentCode() {
return showInconsistentCode;
}
@@ -82,16 +124,14 @@ public class JadxArgs implements IJadxArgs {
this.showInconsistentCode = showInconsistentCode;
}
@Override
public boolean isVerbose() {
return isVerbose;
public boolean isUseImports() {
return useImports;
}
public void setVerbose(boolean verbose) {
isVerbose = verbose;
public void setUseImports(boolean useImports) {
this.useImports = useImports;
}
@Override
public boolean isSkipResources() {
return isSkipResources;
}
@@ -100,7 +140,6 @@ public class JadxArgs implements IJadxArgs {
isSkipResources = skipResources;
}
@Override
public boolean isSkipSources() {
return isSkipSources;
}
@@ -109,7 +148,6 @@ public class JadxArgs implements IJadxArgs {
isSkipSources = skipSources;
}
@Override
public boolean isDeobfuscationOn() {
return isDeobfuscationOn;
}
@@ -118,7 +156,6 @@ public class JadxArgs implements IJadxArgs {
isDeobfuscationOn = deobfuscationOn;
}
@Override
public boolean isDeobfuscationForceSave() {
return isDeobfuscationForceSave;
}
@@ -127,8 +164,7 @@ public class JadxArgs implements IJadxArgs {
isDeobfuscationForceSave = deobfuscationForceSave;
}
@Override
public boolean useSourceNameAsClassAlias() {
public boolean isUseSourceNameAsClassAlias() {
return useSourceNameAsClassAlias;
}
@@ -136,7 +172,6 @@ public class JadxArgs implements IJadxArgs {
this.useSourceNameAsClassAlias = useSourceNameAsClassAlias;
}
@Override
public int getDeobfuscationMinLength() {
return deobfuscationMinLength;
}
@@ -145,7 +180,6 @@ public class JadxArgs implements IJadxArgs {
this.deobfuscationMinLength = deobfuscationMinLength;
}
@Override
public int getDeobfuscationMaxLength() {
return deobfuscationMaxLength;
}
@@ -154,8 +188,7 @@ public class JadxArgs implements IJadxArgs {
this.deobfuscationMaxLength = deobfuscationMaxLength;
}
@Override
public boolean escapeUnicode() {
public boolean isEscapeUnicode() {
return escapeUnicode;
}
@@ -163,7 +196,6 @@ public class JadxArgs implements IJadxArgs {
this.escapeUnicode = escapeUnicode;
}
@Override
public boolean isReplaceConsts() {
return replaceConsts;
}
@@ -172,7 +204,6 @@ public class JadxArgs implements IJadxArgs {
this.replaceConsts = replaceConsts;
}
@Override
public boolean isExportAsGradleProject() {
return exportAsGradleProject;
}
@@ -180,4 +211,31 @@ public class JadxArgs implements IJadxArgs {
public void setExportAsGradleProject(boolean exportAsGradleProject) {
this.exportAsGradleProject = exportAsGradleProject;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("JadxArgs{");
sb.append("inputFiles=").append(inputFiles);
sb.append(", outDir=").append(outDir);
sb.append(", outDirSrc=").append(outDirSrc);
sb.append(", outDirRes=").append(outDirRes);
sb.append(", threadsCount=").append(threadsCount);
sb.append(", cfgOutput=").append(cfgOutput);
sb.append(", rawCFGOutput=").append(rawCFGOutput);
sb.append(", fallbackMode=").append(fallbackMode);
sb.append(", showInconsistentCode=").append(showInconsistentCode);
sb.append(", useImports=").append(useImports);
sb.append(", isSkipResources=").append(isSkipResources);
sb.append(", isSkipSources=").append(isSkipSources);
sb.append(", isDeobfuscationOn=").append(isDeobfuscationOn);
sb.append(", isDeobfuscationForceSave=").append(isDeobfuscationForceSave);
sb.append(", useSourceNameAsClassAlias=").append(useSourceNameAsClassAlias);
sb.append(", deobfuscationMinLength=").append(deobfuscationMinLength);
sb.append(", deobfuscationMaxLength=").append(deobfuscationMaxLength);
sb.append(", escapeUnicode=").append(escapeUnicode);
sb.append(", replaceConsts=").append(replaceConsts);
sb.append(", exportAsGradleProject=").append(exportAsGradleProject);
sb.append('}');
return sb.toString();
}
}
@@ -0,0 +1,108 @@
package jadx.api;
import java.io.File;
import java.util.List;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.core.utils.exceptions.JadxArgsValidateException;
public class JadxArgsValidator {
private static final Logger LOG = LoggerFactory.getLogger(JadxArgsValidator.class);
public static void validate(JadxArgs args) {
checkInputFiles(args);
validateOutDirs(args);
if (LOG.isDebugEnabled()) {
LOG.debug("Effective jadx args: {}", args);
}
}
private static void checkInputFiles(JadxArgs args) {
List<File> inputFiles = args.getInputFiles();
if (inputFiles.isEmpty()) {
throw new JadxArgsValidateException("Please specify input file");
}
if (inputFiles.size() > 1) {
for (File inputFile : inputFiles) {
String fileName = inputFile.getName();
if (fileName.startsWith("--")) {
throw new JadxArgsValidateException("Unknown argument: " + fileName);
}
}
throw new JadxArgsValidateException("Only one input file supported");
}
for (File file : inputFiles) {
checkFile(file);
}
}
private static void validateOutDirs(JadxArgs args) {
File outDir = args.getOutDir();
File srcDir = args.getOutDirSrc();
File resDir = args.getOutDirRes();
if (outDir == null) {
if (srcDir != null) {
outDir = srcDir;
} else if (resDir != null) {
outDir = resDir;
} else {
outDir = makeDirFromInput(args);
}
}
args.setOutDir(outDir);
setFromOut(args);
checkDir(args.getOutDir());
checkDir(args.getOutDirSrc());
checkDir(args.getOutDirRes());
}
@NotNull
private static File makeDirFromInput(JadxArgs args) {
File outDir;
String outDirName;
File file = args.getInputFiles().get(0);
String name = file.getName();
int pos = name.lastIndexOf('.');
if (pos != -1) {
outDirName = name.substring(0, pos);
} else {
outDirName = name + "-" + JadxArgs.DEFAULT_OUT_DIR;
}
LOG.info("output directory: {}", outDirName);
outDir = new File(outDirName);
return outDir;
}
private static void setFromOut(JadxArgs args) {
if (args.getOutDirSrc() == null) {
args.setOutDirSrc(new File(args.getOutDir(), JadxArgs.DEFAULT_SRC_DIR));
}
if (args.getOutDirRes() == null) {
args.setOutDirRes(new File(args.getOutDir(), JadxArgs.DEFAULT_RES_DIR));
}
}
private static void checkFile(File file) {
if (!file.exists()) {
throw new JadxArgsValidateException("File not found " + file.getAbsolutePath());
}
if (file.isDirectory()) {
throw new JadxArgsValidateException("Expected file but found directory instead: " + file.getAbsolutePath());
}
}
private static void checkDir(File dir) {
if (dir != null && dir.exists() && !dir.isDirectory()) {
throw new JadxArgsValidateException("Output directory exists as file " + dir);
}
}
private JadxArgsValidator() {
}
}
@@ -1,5 +1,20 @@
package jadx.api;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.core.Jadx;
import jadx.core.ProcessClass;
import jadx.core.codegen.CodeGen;
@@ -11,38 +26,24 @@ import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.visitors.IDexTreeVisitor;
import jadx.core.dex.visitors.SaveCode;
import jadx.core.export.ExportGradleProject;
import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.utils.files.InputFile;
import jadx.core.xmlgen.BinaryXMLParser;
import jadx.core.xmlgen.ResourcesSaver;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Jadx API usage example:
* <pre><code>
* JadxDecompiler jadx = new JadxDecompiler();
* jadx.loadFile(new File("classes.dex"));
* jadx.setOutputDir(new File("out"));
* jadx.save();
* JadxArgs args = new JadxArgs();
* args.getInputFiles().add(new File("test.apk"));
* args.setOutDir(new File("jadx-test-output"));
*
* JadxDecompiler jadx = new JadxDecompiler(args);
* jadx.load();
* jadx.save();
* </code></pre>
* <p/>
* Instead of 'save()' you can get list of decompiled classes:
* Instead of 'save()' you can iterate over decompiled classes:
* <pre><code>
* for(JavaClass cls : jadx.getClasses()) {
* System.out.println(cls.getCode());
@@ -52,10 +53,9 @@ import org.slf4j.LoggerFactory;
public final class JadxDecompiler {
private static final Logger LOG = LoggerFactory.getLogger(JadxDecompiler.class);
private final IJadxArgs args;
private final List<InputFile> inputFiles = new ArrayList<InputFile>();
private JadxArgs args;
private File outDir;
private final List<InputFile> inputFiles = new ArrayList<>();
private RootNode root;
private List<IDexTreeVisitor> passes;
@@ -66,32 +66,39 @@ public final class JadxDecompiler {
private BinaryXMLParser xmlParser;
private Map<ClassNode, JavaClass> classesMap = new HashMap<ClassNode, JavaClass>();
private Map<MethodNode, JavaMethod> methodsMap = new HashMap<MethodNode, JavaMethod>();
private Map<FieldNode, JavaField> fieldsMap = new HashMap<FieldNode, JavaField>();
private Map<ClassNode, JavaClass> classesMap = new ConcurrentHashMap<>();
private Map<MethodNode, JavaMethod> methodsMap = new ConcurrentHashMap<>();
private Map<FieldNode, JavaField> fieldsMap = new ConcurrentHashMap<>();
public JadxDecompiler() {
this(new JadxArgs());
}
public JadxDecompiler(IJadxArgs jadxArgs) {
this.args = jadxArgs;
this.outDir = jadxArgs.getOutDir();
reset();
init();
public JadxDecompiler(JadxArgs args) {
this.args = args;
}
public void setOutputDir(File outDir) {
this.outDir = outDir;
public void load() {
reset();
JadxArgsValidator.validate(args);
init();
LOG.info("loading ...");
loadFiles(args.getInputFiles());
root = new RootNode(args);
root.load(inputFiles);
root.initClassPath();
root.loadResources(getResources());
root.initAppResClass();
initVisitors();
}
void init() {
if (outDir == null) {
outDir = new JadxArgs().getOutDir();
}
this.passes = Jadx.getPassesList(args, outDir);
this.codeGen = new CodeGen(args);
this.passes = Jadx.getPassesList(args);
this.codeGen = new CodeGen();
}
void reset() {
@@ -107,23 +114,18 @@ public final class JadxDecompiler {
return Jadx.getVersion();
}
public void loadFile(File file) throws JadxException {
loadFiles(Collections.singletonList(file));
}
public void loadFiles(List<File> files) throws JadxException {
private void loadFiles(List<File> files) {
if (files.isEmpty()) {
throw new JadxException("Empty file list");
throw new JadxRuntimeException("Empty file list");
}
inputFiles.clear();
for (File file : files) {
try {
InputFile.addFilesFrom(file, inputFiles);
} catch (IOException e) {
throw new JadxException("Error load file: " + file, e);
InputFile.addFilesFrom(file, inputFiles, args.isSkipSources());
} catch (Exception e) {
throw new JadxRuntimeException("Error load file: " + file, e);
}
}
parse();
}
public void save() {
@@ -139,12 +141,13 @@ public final class JadxDecompiler {
}
private void save(boolean saveSources, boolean saveResources) {
ExecutorService ex = getSaveExecutor(saveSources, saveResources);
ex.shutdown();
try {
ExecutorService ex = getSaveExecutor(saveSources, saveResources);
ex.shutdown();
ex.awaitTermination(1, TimeUnit.DAYS);
} catch (InterruptedException e) {
throw new JadxRuntimeException("Save interrupted", e);
LOG.error("Save interrupted", e);
Thread.currentThread().interrupt();
}
}
@@ -165,13 +168,13 @@ public final class JadxDecompiler {
File sourcesOutDir;
File resOutDir;
if (args.isExportAsGradleProject()) {
ExportGradleProject export = new ExportGradleProject(root, outDir);
ExportGradleProject export = new ExportGradleProject(root, args.getOutDir());
export.init();
sourcesOutDir = export.getSrcOutDir();
resOutDir = export.getResOutDir();
} else {
sourcesOutDir = outDir;
resOutDir = outDir;
sourcesOutDir = args.getOutDirSrc();
resOutDir = args.getOutDirRes();
}
if (saveSources) {
appendSourcesSave(executor, sourcesOutDir);
@@ -188,17 +191,14 @@ public final class JadxDecompiler {
}
}
private void appendSourcesSave(ExecutorService executor, final File outDir) {
for (final JavaClass cls : getClasses()) {
private void appendSourcesSave(ExecutorService executor, File outDir) {
for (JavaClass cls : getClasses()) {
if (cls.getClassNode().contains(AFlag.DONT_GENERATE)) {
continue;
}
executor.execute(new Runnable() {
@Override
public void run() {
cls.decompile();
SaveCode.save(outDir, args, cls.getClassNode());
}
executor.execute(() -> {
cls.decompile();
SaveCode.save(outDir, args, cls.getClassNode());
});
}
}
@@ -209,7 +209,7 @@ public final class JadxDecompiler {
}
if (classes == null) {
List<ClassNode> classNodeList = root.getClasses(false);
List<JavaClass> clsList = new ArrayList<JavaClass>(classNodeList.size());
List<JavaClass> clsList = new ArrayList<>(classNodeList.size());
classesMap.clear();
for (ClassNode classNode : classNodeList) {
JavaClass javaClass = new JavaClass(classNode, this);
@@ -236,28 +236,19 @@ public final class JadxDecompiler {
if (classList.isEmpty()) {
return Collections.emptyList();
}
Map<String, List<JavaClass>> map = new HashMap<String, List<JavaClass>>();
Map<String, List<JavaClass>> map = new HashMap<>();
for (JavaClass javaClass : classList) {
String pkg = javaClass.getPackage();
List<JavaClass> clsList = map.get(pkg);
if (clsList == null) {
clsList = new ArrayList<JavaClass>();
map.put(pkg, clsList);
}
List<JavaClass> clsList = map.computeIfAbsent(pkg, k -> new ArrayList<>());
clsList.add(javaClass);
}
List<JavaPackage> packages = new ArrayList<JavaPackage>(map.size());
List<JavaPackage> packages = new ArrayList<>(map.size());
for (Map.Entry<String, List<JavaClass>> entry : map.entrySet()) {
packages.add(new JavaPackage(entry.getKey(), entry.getValue()));
}
Collections.sort(packages);
for (JavaPackage pkg : packages) {
Collections.sort(pkg.getClasses(), new Comparator<JavaClass>() {
@Override
public int compare(JavaClass o1, JavaClass o2) {
return o1.getName().compareTo(o2.getName());
}
});
pkg.getClasses().sort(Comparator.comparing(JavaClass::getName));
}
return Collections.unmodifiableList(packages);
}
@@ -277,21 +268,6 @@ public final class JadxDecompiler {
root.getErrorsCounter().printReport();
}
void parse() throws DecodeException {
reset();
init();
root = new RootNode(args);
LOG.info("loading ...");
root.load(inputFiles);
root.initClassPath();
root.loadResources(getResources());
root.initAppResClass();
initVisitors();
}
private void initVisitors() {
for (IDexTreeVisitor pass : passes) {
try {
@@ -329,7 +305,7 @@ public final class JadxDecompiler {
return fieldsMap;
}
public IJadxArgs getArgs() {
public JadxArgs getArgs() {
return args;
}
@@ -337,5 +313,4 @@ public final class JadxDecompiler {
public String toString() {
return "jadx decompiler " + getVersion();
}
}
@@ -72,7 +72,7 @@ public final class JavaClass implements JavaNode {
JadxDecompiler rootDecompiler = getRootDecompiler();
int inClsCount = cls.getInnerClasses().size();
if (inClsCount != 0) {
List<JavaClass> list = new ArrayList<JavaClass>(inClsCount);
List<JavaClass> list = new ArrayList<>(inClsCount);
for (ClassNode inner : cls.getInnerClasses()) {
if (!inner.contains(AFlag.DONT_GENERATE)) {
JavaClass javaClass = new JavaClass(inner, this);
@@ -86,7 +86,7 @@ public final class JavaClass implements JavaNode {
int fieldsCount = cls.getFields().size();
if (fieldsCount != 0) {
List<JavaField> flds = new ArrayList<JavaField>(fieldsCount);
List<JavaField> flds = new ArrayList<>(fieldsCount);
for (FieldNode f : cls.getFields()) {
if (!f.contains(AFlag.DONT_GENERATE)) {
JavaField javaField = new JavaField(f, this);
@@ -99,7 +99,7 @@ public final class JavaClass implements JavaNode {
int methodsCount = cls.getMethods().size();
if (methodsCount != 0) {
List<JavaMethod> mths = new ArrayList<JavaMethod>(methodsCount);
List<JavaMethod> mths = new ArrayList<>(methodsCount);
for (MethodNode m : cls.getMethods()) {
if (!m.contains(AFlag.DONT_GENERATE)) {
JavaMethod javaMethod = new JavaMethod(this, m);
@@ -134,7 +134,7 @@ public final class JavaClass implements JavaNode {
if (map.isEmpty() || decompiler == null) {
return Collections.emptyMap();
}
Map<CodePosition, JavaNode> resultMap = new HashMap<CodePosition, JavaNode>(map.size());
Map<CodePosition, JavaNode> resultMap = new HashMap<>(map.size());
for (Map.Entry<CodePosition, Object> entry : map.entrySet()) {
CodePosition codePosition = entry.getKey();
Object obj = entry.getValue();
@@ -1,5 +1,6 @@
package jadx.api;
import jadx.core.utils.files.ZipSecurity;
import jadx.core.xmlgen.ResContainer;
import java.io.File;
@@ -34,7 +35,7 @@ public class ResourceFile {
private final ResourceType type;
private ZipRef zipRef;
ResourceFile(JadxDecompiler decompiler, String name, ResourceType type) {
protected ResourceFile(JadxDecompiler decompiler, String name, ResourceType type) {
this.decompiler = decompiler;
this.name = name;
this.type = type;
@@ -56,7 +57,7 @@ public class ResourceFile {
this.zipRef = zipRef;
}
ZipRef getZipRef() {
public ZipRef getZipRef() {
return zipRef;
}
@@ -64,4 +65,11 @@ public class ResourceFile {
public String toString() {
return "ResourceFile{name='" + name + '\'' + ", type=" + type + "}";
}
public static ResourceFile createResourceFileInstance(JadxDecompiler decompiler, String name, ResourceType type) {
if(!ZipSecurity.isValidZipEntryName(name)) {
return null;
}
return new ResourceFile(decompiler, name, type);
}
}
@@ -1,13 +1,14 @@
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;
public ResourceFileContent(String name, ResourceType type, CodeWriter content) {
private ResourceFileContent(String name, ResourceType type, CodeWriter content) {
super(null, name, type);
this.content = content;
}
@@ -16,4 +17,11 @@ public class ResourceFileContent extends ResourceFile {
public ResContainer loadContent() {
return ResContainer.singleFile(getName(), content);
}
public static ResourceFileContent createResourceFileContentInstance(String name, ResourceType type, CodeWriter content) {
if(!ZipSecurity.isValidZipEntryName(name)) {
return null;
}
return new ResourceFileContent(name, type, content);
}
}
@@ -1,16 +1,9 @@
package jadx.api;
import jadx.api.ResourceFile.ZipRef;
import jadx.core.codegen.CodeWriter;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.files.InputFile;
import jadx.core.xmlgen.ResContainer;
import jadx.core.xmlgen.ResTableParser;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
@@ -22,8 +15,16 @@ import java.util.zip.ZipFile;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.ResourceFile.ZipRef;
import jadx.core.codegen.CodeWriter;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.files.InputFile;
import jadx.core.utils.files.ZipSecurity;
import jadx.core.xmlgen.ResContainer;
import jadx.core.xmlgen.ResTableParser;
import static jadx.core.utils.files.FileUtils.READ_BUFFER_SIZE;
import static jadx.core.utils.files.FileUtils.close;
import static jadx.core.utils.files.FileUtils.copyStream;
// TODO: move to core package
@@ -39,7 +40,7 @@ public final class ResourcesLoader {
}
List<ResourceFile> load(List<InputFile> inputFiles) {
List<ResourceFile> list = new ArrayList<ResourceFile>(inputFiles.size());
List<ResourceFile> list = new ArrayList<>(inputFiles.size());
for (InputFile file : inputFiles) {
loadFile(list, file.getFile());
}
@@ -51,44 +52,35 @@ public final class ResourcesLoader {
}
public static ResContainer decodeStream(ResourceFile rf, ResourceDecoder decoder) throws JadxException {
ZipRef zipRef = rf.getZipRef();
if (zipRef == null) {
return null;
}
ZipFile zipFile = null;
InputStream inputStream = null;
ResContainer result = null;
try {
zipFile = new ZipFile(zipRef.getZipFile());
ZipEntry entry = zipFile.getEntry(zipRef.getEntryName());
if (entry == null) {
throw new IOException("Zip entry not found: " + zipRef);
}
inputStream = new BufferedInputStream(zipFile.getInputStream(entry));
result = decoder.decode(entry.getSize(), inputStream);
} catch (Exception e) {
throw new JadxException("Error decode: " + zipRef.getEntryName(), e);
} finally {
try {
if (zipFile != null) {
zipFile.close();
ZipRef zipRef = rf.getZipRef();
if (zipRef == null) {
File file = new File(rf.getName());
try (InputStream inputStream = new BufferedInputStream(new FileInputStream(file))) {
return decoder.decode(file.length(), inputStream);
}
} else {
try (ZipFile zipFile = new ZipFile(zipRef.getZipFile())) {
ZipEntry entry = zipFile.getEntry(zipRef.getEntryName());
if (entry == null) {
throw new IOException("Zip entry not found: " + zipRef);
}
if (!ZipSecurity.isValidZipEntry(entry)) {
return null;
}
try (InputStream inputStream = new BufferedInputStream(zipFile.getInputStream(entry))) {
return decoder.decode(entry.getSize(), inputStream);
}
}
} catch (Exception e) {
LOG.error("Error close zip file: {}", zipRef, e);
}
close(inputStream);
} catch (Exception e) {
throw new JadxException("Error decode: " + rf.getName(), e);
}
return result;
}
static ResContainer loadContent(final JadxDecompiler jadxRef, final ResourceFile rf) {
static ResContainer loadContent(JadxDecompiler jadxRef, ResourceFile rf) {
try {
return decodeStream(rf, new ResourceDecoder() {
@Override
public ResContainer decode(long size, InputStream is) throws IOException {
return loadContent(jadxRef, rf, is, size);
}
});
return decodeStream(rf, (size, is) -> loadContent(jadxRef, rf, is, size));
} catch (JadxException e) {
LOG.error("Decode error", e);
CodeWriter cw = new CodeWriter();
@@ -99,7 +91,7 @@ public final class ResourcesLoader {
}
private static ResContainer loadContent(JadxDecompiler jadxRef, ResourceFile rf,
InputStream inputStream, long size) throws IOException {
InputStream inputStream, long size) throws IOException {
switch (rf.getType()) {
case MANIFEST:
case XML:
@@ -111,36 +103,40 @@ public final class ResourcesLoader {
case IMG:
return ResContainer.singleImageFile(rf.getName(), inputStream);
default:
if (size > LOAD_SIZE_LIMIT) {
return ResContainer.singleFile(rf.getName(),
new CodeWriter().add("File too big, size: " + String.format("%.2f KB", size / 1024.)));
}
return ResContainer.singleFile(rf.getName(), loadToCodeWriter(inputStream));
}
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));
}
private void loadFile(List<ResourceFile> list, File file) {
if (file == null) {
return;
}
ZipFile zip = null;
try {
zip = new ZipFile(file);
try (ZipFile zip = new ZipFile(file)) {
Enumeration<? extends ZipEntry> entries = zip.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
addEntry(list, file, entry);
}
} catch (IOException e) {
LOG.debug("Not a zip file: {}", file.getAbsolutePath());
} finally {
if (zip != null) {
try {
zip.close();
} catch (Exception e) {
LOG.error("Zip file close error: {}", file.getAbsolutePath(), e);
if (ZipSecurity.isValidZipEntry(entry)) {
addEntry(list, file, entry);
}
}
} catch (Exception e) {
LOG.debug("Not a zip file: {}", file.getAbsolutePath());
addResourceFile(list, file);
}
}
private void addResourceFile(List<ResourceFile> list, File file) {
String name = file.getAbsolutePath();
ResourceType type = ResourceType.getFileType(name);
ResourceFile rf = ResourceFile.createResourceFileInstance(jadxRef, name, type);
if (rf != null) {
list.add(rf);
}
}
@@ -150,9 +146,11 @@ public final class ResourcesLoader {
}
String name = entry.getName();
ResourceType type = ResourceType.getFileType(name);
ResourceFile rf = new ResourceFile(jadxRef, name, type);
rf.setZipRef(new ZipRef(zipFile, name));
list.add(rf);
ResourceFile rf = ResourceFile.createResourceFileInstance(jadxRef, name, type);
if (rf != null) {
rf.setZipRef(new ZipRef(zipFile, name));
list.add(rf);
}
}
public static CodeWriter loadToCodeWriter(InputStream is) throws IOException {
@@ -21,4 +21,7 @@ public class Consts {
public static final String ANONYMOUS_CLASS_PREFIX = "AnonymousClass";
public static final String MTH_TOSTRING_SIGNATURE = "toString()Ljava/lang/String;";
private Consts() {
}
}
+20 -18
View File
@@ -1,6 +1,15 @@
package jadx.core;
import jadx.api.IJadxArgs;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.Manifest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.JadxArgs;
import jadx.core.dex.visitors.ClassModifier;
import jadx.core.dex.visitors.CodeShrinker;
import jadx.core.dex.visitors.ConstInlineVisitor;
@@ -33,27 +42,20 @@ import jadx.core.dex.visitors.ssa.SSATransform;
import jadx.core.dex.visitors.typeinference.FinishTypeInference;
import jadx.core.dex.visitors.typeinference.TypeInference;
import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.Manifest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Jadx {
private static final Logger LOG = LoggerFactory.getLogger(Jadx.class);
private Jadx() {
}
static {
if (Consts.DEBUG) {
LOG.info("debug enabled");
}
}
public static List<IDexTreeVisitor> getPassesList(IJadxArgs args, File outDir) {
List<IDexTreeVisitor> passes = new ArrayList<IDexTreeVisitor>();
public static List<IDexTreeVisitor> getPassesList(JadxArgs args) {
List<IDexTreeVisitor> passes = new ArrayList<>();
if (args.isFallbackMode()) {
passes.add(new FallbackModeVisitor());
} else {
@@ -68,7 +70,7 @@ public class Jadx {
passes.add(new TypeInference());
if (args.isRawCFGOutput()) {
passes.add(DotGraphVisitor.dumpRaw(outDir));
passes.add(DotGraphVisitor.dumpRaw());
}
passes.add(new ConstInlineVisitor());
@@ -80,8 +82,8 @@ public class Jadx {
passes.add(new CodeShrinker());
passes.add(new ReSugarCode());
if (args.isCFGOutput()) {
passes.add(DotGraphVisitor.dump(outDir));
if (args.isCfgOutput()) {
passes.add(DotGraphVisitor.dump());
}
passes.add(new RegionMakerVisitor());
@@ -92,8 +94,8 @@ public class Jadx {
passes.add(new SimplifyVisitor());
passes.add(new CheckRegions());
if (args.isCFGOutput()) {
passes.add(DotGraphVisitor.dumpRegions(outDir));
if (args.isCfgOutput()) {
passes.add(DotGraphVisitor.dumpRegions());
}
passes.add(new MethodInlineVisitor());
@@ -9,8 +9,6 @@ import jadx.core.utils.ErrorsCounter;
import java.util.List;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static jadx.core.dex.nodes.ProcessState.GENERATED;
import static jadx.core.dex.nodes.ProcessState.NOT_LOADED;
@@ -19,7 +17,6 @@ import static jadx.core.dex.nodes.ProcessState.STARTED;
import static jadx.core.dex.nodes.ProcessState.UNLOADED;
public final class ProcessClass {
private static final Logger LOG = LoggerFactory.getLogger(ProcessClass.class);
private ProcessClass() {
}
@@ -28,7 +25,7 @@ public final class ProcessClass {
if (codeGen == null && cls.getState() == PROCESSED) {
return;
}
synchronized (cls) {
synchronized (cls.getClassInfo()) {
try {
if (cls.getState() == NOT_LOADED) {
cls.load();
@@ -54,7 +51,7 @@ public final class ProcessClass {
}
}
static void processDependencies(ClassNode cls, List<IDexTreeVisitor> passes) {
private static void processDependencies(ClassNode cls, List<IDexTreeVisitor> passes) {
for (ClassNode depCls : cls.getDependencies()) {
process(depCls, passes, null);
}
@@ -6,6 +6,7 @@ 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 java.io.BufferedOutputStream;
import java.io.DataInputStream;
@@ -48,7 +49,7 @@ public class ClsSet {
public void load(RootNode root) {
List<ClassNode> list = root.getClasses(true);
Map<String, NClass> names = new HashMap<String, NClass>(list.size());
Map<String, NClass> names = new HashMap<>(list.size());
int k = 0;
for (ClassNode cls : list) {
String clsRawName = cls.getRawName();
@@ -78,7 +79,7 @@ public class ClsSet {
}
public static NClass[] makeParentsArray(ClassNode cls, Map<String, NClass> names) {
List<NClass> parents = new ArrayList<NClass>(1 + cls.getInterfaces().size());
List<NClass> parents = new ArrayList<>(1 + cls.getInterfaces().size());
ArgType superClass = cls.getSuperClass();
if (superClass != null) {
NClass c = getCls(superClass.getObject(), names);
@@ -105,9 +106,7 @@ public class ClsSet {
void save(File output) throws IOException {
FileUtils.makeDirsForFile(output);
BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(output));
try {
try (BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(output))) {
String outputName = output.getName();
if (outputName.endsWith(CLST_EXTENSION)) {
save(outputStream);
@@ -122,14 +121,11 @@ public class ClsSet {
} else {
throw new JadxRuntimeException("Unknown file format: " + outputName);
}
} finally {
close(outputStream);
}
}
public void save(OutputStream output) throws IOException {
DataOutputStream out = new DataOutputStream(output);
try {
try (DataOutputStream out = new DataOutputStream(output)) {
out.writeBytes(JADX_CLS_SET_HEADER);
out.writeByte(VERSION);
@@ -145,53 +141,41 @@ public class ClsSet {
out.writeInt(parent.getId());
}
}
} finally {
close(out);
}
}
public void load() throws IOException, DecodeException {
InputStream input = getClass().getResourceAsStream(CLST_FILENAME);
if (input == null) {
throw new JadxRuntimeException("Can't load classpath file: " + CLST_FILENAME);
}
try {
try (InputStream input = getClass().getResourceAsStream(CLST_FILENAME)) {
if (input == null) {
throw new JadxRuntimeException("Can't load classpath file: " + CLST_FILENAME);
}
load(input);
} finally {
close(input);
}
}
public void load(File input) throws IOException, DecodeException {
String name = input.getName();
InputStream inputStream = new FileInputStream(input);
try {
try (InputStream inputStream = new FileInputStream(input)) {
if (name.endsWith(CLST_EXTENSION)) {
load(inputStream);
} else if (name.endsWith(".jar")) {
ZipInputStream in = new ZipInputStream(inputStream);
try {
try (ZipInputStream in = new ZipInputStream(inputStream)) {
ZipEntry entry = in.getNextEntry();
while (entry != null) {
if (entry.getName().endsWith(CLST_EXTENSION)) {
if (entry.getName().endsWith(CLST_EXTENSION) && ZipSecurity.isValidZipEntry(entry)) {
load(in);
}
entry = in.getNextEntry();
}
} finally {
close(in);
}
} else {
throw new JadxRuntimeException("Unknown file format: " + name);
}
} finally {
close(inputStream);
}
}
public void load(InputStream input) throws IOException, DecodeException {
DataInputStream in = new DataInputStream(input);
try {
try (DataInputStream in = new DataInputStream(input)) {
byte[] header = new byte[JADX_CLS_SET_HEADER.length()];
int readHeaderLength = in.read(header);
int version = in.readByte();
@@ -214,8 +198,6 @@ public class ClsSet {
}
classes[i].setParents(parents);
}
} finally {
close(in);
}
}
@@ -23,10 +23,10 @@ import org.slf4j.LoggerFactory;
public class ClspGraph {
private static final Logger LOG = LoggerFactory.getLogger(ClspGraph.class);
private final Map<String, Set<String>> ancestorCache = new WeakHashMap<String, Set<String>>();
private final Map<String, Set<String>> ancestorCache = Collections.synchronizedMap(new WeakHashMap<String, Set<String>>());
private Map<String, NClass> nameMap;
private final Set<String> missingClasses = new HashSet<String>();
private final Set<String> missingClasses = new HashSet<>();
public void load() throws IOException, DecodeException {
ClsSet set = new ClsSet();
@@ -36,7 +36,7 @@ public class ClspGraph {
public void addClasspath(ClsSet set) {
if (nameMap == null) {
nameMap = new HashMap<String, NClass>(set.getClassesCount());
nameMap = new HashMap<>(set.getClassesCount());
set.addToMap(nameMap);
} else {
throw new JadxRuntimeException("Classpath already loaded");
@@ -110,7 +110,7 @@ public class ClspGraph {
missingClasses.add(clsName);
return Collections.emptySet();
}
result = new HashSet<String>();
result = new HashSet<>();
addAncestorsNames(cls, result);
if (result.isEmpty()) {
result = Collections.emptySet();
@@ -133,7 +133,7 @@ public class ClspGraph {
}
LOG.warn("Found {} references to unknown classes", count);
if (LOG.isDebugEnabled()) {
List<String> clsNames = new ArrayList<String>(missingClasses);
List<String> clsNames = new ArrayList<>(missingClasses);
Collections.sort(clsNames);
for (String cls : clsNames) {
LOG.debug(" {}", cls);
@@ -30,7 +30,7 @@ public class ConvertToClsSet {
}
File output = new File(args[0]);
List<InputFile> inputFiles = new ArrayList<InputFile>(args.length - 1);
List<InputFile> inputFiles = new ArrayList<>(args.length - 1);
for (int i = 1; i < args.length; i++) {
File f = new File(args[i]);
if (f.isDirectory()) {
@@ -1,11 +1,24 @@
package jadx.core.codegen;
import jadx.api.IJadxArgs;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import com.android.dx.rop.code.AccessFlags;
import jadx.api.JadxArgs;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.AttrNode;
import jadx.core.dex.attributes.nodes.EnumClassAttr;
import jadx.core.dex.attributes.nodes.EnumClassAttr.EnumField;
import jadx.core.dex.attributes.nodes.LineAttrNode;
import jadx.core.dex.attributes.nodes.SourceFileAttr;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.info.ClassInfo;
@@ -23,52 +36,31 @@ import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.CodegenException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.android.dx.rop.code.AccessFlags;
public class ClassGen {
private static final Logger LOG = LoggerFactory.getLogger(ClassGen.class);
public static final Comparator<MethodNode> METHOD_LINE_COMPARATOR = new Comparator<MethodNode>() {
@Override
public int compare(MethodNode a, MethodNode b) {
return Utils.compare(a.getSourceLine(), b.getSourceLine());
}
};
private final ClassNode cls;
private final ClassGen parentGen;
private final AnnotationGen annotationGen;
private final boolean fallback;
private final boolean useImports;
private final boolean showInconsistentCode;
private final Set<ClassInfo> imports = new HashSet<ClassInfo>();
private final Set<ClassInfo> imports = new HashSet<>();
private int clsDeclLine;
public ClassGen(ClassNode cls, IJadxArgs jadxArgs) {
this(cls, null, jadxArgs.isFallbackMode(), jadxArgs.isShowInconsistentCode());
public ClassGen(ClassNode cls, JadxArgs jadxArgs) {
this(cls, null, jadxArgs.isUseImports(), jadxArgs.isFallbackMode(), jadxArgs.isShowInconsistentCode());
}
public ClassGen(ClassNode cls, ClassGen parentClsGen) {
this(cls, parentClsGen, parentClsGen.fallback, parentClsGen.showInconsistentCode);
this(cls, parentClsGen, parentClsGen.useImports, parentClsGen.fallback, parentClsGen.showInconsistentCode);
}
public ClassGen(ClassNode cls, ClassGen parentClsGen, boolean fallback, boolean showBadCode) {
public ClassGen(ClassNode cls, ClassGen parentClsGen, boolean useImports, boolean fallback, boolean showBadCode) {
this.cls = cls;
this.parentGen = parentClsGen;
this.fallback = fallback;
this.useImports = useImports;
this.showInconsistentCode = showBadCode;
this.annotationGen = new AnnotationGen(cls, this);
@@ -89,7 +81,7 @@ public class ClassGen {
}
int importsCount = imports.size();
if (importsCount != 0) {
List<String> sortImports = new ArrayList<String>(importsCount);
List<String> sortImports = new ArrayList<>(importsCount);
for (ClassInfo ic : imports) {
sortImports.add(ic.getAlias().getFullName());
}
@@ -273,8 +265,8 @@ public class ClassGen {
}
private static List<MethodNode> sortMethodsByLine(List<MethodNode> methods) {
List<MethodNode> out = new ArrayList<MethodNode>(methods);
Collections.sort(out, METHOD_LINE_COMPARATOR);
List<MethodNode> out = new ArrayList<>(methods);
out.sort(Comparator.comparingInt(LineAttrNode::getSourceLine));
return out;
}
@@ -339,6 +331,10 @@ public class ClassGen {
continue;
}
annotationGen.addForField(code, f);
if (f.getFieldInfo().isRenamed()) {
code.startLine("/* renamed from: ").add(f.getName()).add(" */");
}
code.startLine(f.getAccessFlags().makeString());
useType(code, f.getType());
code.add(' ');
@@ -476,7 +472,7 @@ public class ClassGen {
private String useClassInternal(ClassInfo useCls, ClassInfo extClsInfo) {
String fullName = extClsInfo.getFullName();
if (fallback) {
if (fallback || !useImports) {
return fullName;
}
String shortName = extClsInfo.getShortName();
@@ -586,9 +582,8 @@ public class ClassGen {
private void insertRenameInfo(CodeWriter code, ClassNode cls) {
ClassInfo classInfo = cls.getClassInfo();
if (classInfo.isRenamed()
&& !cls.getShortName().equals(cls.getAlias().getShortName())) {
code.startLine("/* renamed from: ").add(classInfo.getFullName()).add(" */");
if (classInfo.isRenamed()) {
code.startLine("/* renamed from: ").add(classInfo.getType().getObject()).add(" */");
}
}
@@ -1,25 +1,17 @@
package jadx.core.codegen;
import jadx.api.IJadxArgs;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.visitors.AbstractVisitor;
import jadx.core.utils.exceptions.CodegenException;
public class CodeGen extends AbstractVisitor {
private final IJadxArgs args;
public CodeGen(IJadxArgs args) {
this.args = args;
}
@Override
public boolean visit(ClassNode cls) throws CodegenException {
ClassGen clsGen = new ClassGen(cls, args);
ClassGen clsGen = new ClassGen(cls, cls.root().getArgs());
CodeWriter clsCode = clsGen.makeClass();
clsCode.finish();
cls.setCode(clsCode);
return false;
}
}
@@ -3,6 +3,7 @@ package jadx.core.codegen;
import jadx.api.CodePosition;
import jadx.core.dex.attributes.nodes.LineAttrNode;
import jadx.core.utils.files.FileUtils;
import jadx.core.utils.files.ZipSecurity;
import java.io.File;
import java.io.PrintWriter;
@@ -16,23 +17,21 @@ import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static jadx.core.utils.files.FileUtils.close;
public class CodeWriter {
private static final Logger LOG = LoggerFactory.getLogger(CodeWriter.class);
public static final String NL = System.getProperty("line.separator");
public static final String INDENT = " ";
public static final String INDENT_STR = " ";
private static final boolean ADD_LINE_NUMBERS = false;
private static final String[] INDENT_CACHE = {
"",
INDENT,
INDENT + INDENT,
INDENT + INDENT + INDENT,
INDENT + INDENT + INDENT + INDENT,
INDENT + INDENT + INDENT + INDENT + INDENT,
INDENT_STR,
INDENT_STR + INDENT_STR,
INDENT_STR + INDENT_STR + INDENT_STR,
INDENT_STR + INDENT_STR + INDENT_STR + INDENT_STR,
INDENT_STR + INDENT_STR + INDENT_STR + INDENT_STR + INDENT_STR,
};
private StringBuilder buf = new StringBuilder();
@@ -127,7 +126,7 @@ public class CodeWriter {
}
public CodeWriter addIndent() {
add(INDENT);
add(INDENT_STR);
return this;
}
@@ -148,9 +147,9 @@ public class CodeWriter {
if (curIndent < INDENT_CACHE.length) {
this.indentStr = INDENT_CACHE[curIndent];
} else {
StringBuilder s = new StringBuilder(curIndent * INDENT.length());
StringBuilder s = new StringBuilder(curIndent * INDENT_STR.length());
for (int i = 0; i < curIndent; i++) {
s.append(INDENT);
s.append(INDENT_STR);
}
this.indentStr = s.toString();
}
@@ -209,7 +208,7 @@ public class CodeWriter {
private Object attachAnnotation(Object obj, CodePosition pos) {
if (annotations.isEmpty()) {
annotations = new HashMap<CodePosition, Object>();
annotations = new HashMap<>();
}
return annotations.put(pos, obj);
}
@@ -227,7 +226,7 @@ public class CodeWriter {
private void attachSourceLine(int decompiledLine, int sourceLine) {
if (lineMap.isEmpty()) {
lineMap = new TreeMap<Integer, Integer>();
lineMap = new TreeMap<>();
}
lineMap.put(decompiledLine, sourceLine);
}
@@ -274,10 +273,16 @@ public class CodeWriter {
}
public void save(File dir, String subDir, String fileName) {
if(!ZipSecurity.isValidZipEntryName(subDir) || !ZipSecurity.isValidZipEntryName(fileName)) {
return;
}
save(dir, new File(subDir, fileName).getPath());
}
public void save(File dir, String fileName) {
if(!ZipSecurity.isValidZipEntryName(fileName)) {
return;
}
save(new File(dir, fileName));
}
@@ -286,15 +291,10 @@ public class CodeWriter {
finish();
}
File outFile = FileUtils.prepareFile(file);
PrintWriter out = null;
try {
out = new PrintWriter(outFile, "UTF-8");
try (PrintWriter out = new PrintWriter(outFile, "UTF-8")) {
out.println(code);
} catch (Exception e) {
LOG.error("Save file error", e);
} finally {
close(out);
}
}
}
@@ -1,5 +1,12 @@
package jadx.core.codegen;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Queue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.core.dex.instructions.ArithNode;
import jadx.core.dex.instructions.IfOp;
import jadx.core.dex.instructions.InsnType;
@@ -15,18 +22,11 @@ import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.exceptions.CodegenException;
import jadx.core.utils.exceptions.JadxRuntimeException;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Queue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ConditionGen extends InsnGen {
private static final Logger LOG = LoggerFactory.getLogger(ConditionGen.class);
private static class CondStack {
private final Queue<IfCondition> stack = new LinkedList<IfCondition>();
private final Queue<IfCondition> stack = new LinkedList<>();
public Queue<IfCondition> getStack() {
return stack;
@@ -126,7 +126,7 @@ public class ConditionGen extends InsnGen {
wrap(code, firstArg);
return;
}
LOG.warn(ErrorsCounter.formatErrorMsg(mth, "Unsupported boolean condition " + op.getSymbol()));
ErrorsCounter.methodError(mth, "Unsupported boolean condition " + op.getSymbol());
}
addArg(code, firstArg, isArgWrapNeeded(firstArg));
@@ -179,6 +179,9 @@ public class ConditionGen extends InsnGen {
case DIV:
case REM:
return false;
default:
return true;
}
} else {
switch (insnType) {
@@ -189,10 +192,10 @@ public class ConditionGen extends InsnGen {
case CONST:
case ARRAY_LENGTH:
return false;
default:
return true;
}
}
return true;
}
}
@@ -270,18 +270,13 @@ public class InsnGen {
makeArith((ArithNode) insn, code, state);
break;
case NEG: {
boolean wrap = state.contains(Flags.BODY_ONLY);
if (wrap) {
code.add('(');
}
code.add('-');
addArg(code, insn.getArg(0));
if (wrap) {
code.add(')');
}
case NEG:
oneArgInsn(code, insn, state, '-');
break;
case NOT:
oneArgInsn(code, insn, state, '~');
break;
}
case RETURN:
if (insn.getArgsCount() != 0) {
@@ -525,6 +520,18 @@ public class InsnGen {
}
}
private void oneArgInsn(CodeWriter code, InsnNode insn, Set<Flags> state, char op) throws CodegenException {
boolean wrap = state.contains(Flags.BODY_ONLY);
if (wrap) {
code.add('(');
}
code.add(op);
addArg(code, insn.getArg(0));
if (wrap) {
code.add(')');
}
}
private void fallbackOnlyInsn(InsnNode insn) throws CodegenException {
if (!fallback) {
throw new CodegenException(insn.getType() + " can be used only in fallback mode");
@@ -752,7 +759,7 @@ public class InsnGen {
}
// replace args
InsnNode inlCopy = inl.copy();
List<RegisterArg> inlArgs = new ArrayList<RegisterArg>();
List<RegisterArg> inlArgs = new ArrayList<>();
inlCopy.getRegisterArgs(inlArgs);
for (RegisterArg r : inlArgs) {
int regNum = r.getRegNum();
@@ -80,6 +80,10 @@ public class MethodGen {
if (clsAccFlags.isAnnotation()) {
ai = ai.remove(AccessFlags.ACC_PUBLIC);
}
if (mth.getMethodInfo().isRenamed()) {
code.startLine("/* renamed from: ").add(mth.getName()).add(" */");
}
code.startLineWithNum(mth.getSourceLine());
code.add(ai.makeString());
@@ -129,7 +133,7 @@ public class MethodGen {
annotationGen.addForParameter(argsCode, paramsAnnotation, i);
}
SSAVar argSVar = arg.getSVar();
if (argSVar!= null && argSVar.contains(AFlag.FINAL)) {
if (argSVar != null && argSVar.contains(AFlag.FINAL)) {
argsCode.add("final ");
}
if (!it.hasNext() && mth.getAccessFlags().isVarArgs()) {
@@ -241,7 +245,7 @@ public class MethodGen {
* Return fallback variant of method codegen
*/
public static MethodGen getFallbackMethodGen(MethodNode mth) {
ClassGen clsGen = new ClassGen(mth.getParentClass(), null, true, true);
ClassGen clsGen = new ClassGen(mth.getParentClass(), null, true, true, true);
return new MethodGen(clsGen, mth);
}
@@ -26,12 +26,12 @@ public class NameGen {
private static final Map<String, String> OBJ_ALIAS;
private final Set<String> varNames = new HashSet<String>();
private final Set<String> varNames = new HashSet<>();
private final MethodNode mth;
private final boolean fallback;
static {
OBJ_ALIAS = new HashMap<String, String>();
OBJ_ALIAS = new HashMap<>();
OBJ_ALIAS.put(Consts.CLASS_STRING, "str");
OBJ_ALIAS.put(Consts.CLASS_CLASS, "cls");
OBJ_ALIAS.put(Consts.CLASS_THROWABLE, "th");
@@ -164,6 +164,9 @@ public class NameGen {
if (vName != null) {
return vName;
}
if (shortName != null) {
return StringUtils.escape(shortName.toLowerCase());
}
}
return StringUtils.escape(type.toString());
}
@@ -207,11 +207,13 @@ public class RegionGen extends InsnGen {
if (region.isConditionAtEnd()) {
code.startLine("do {");
makeRegionIndent(code, region.getBody());
code.startLine("} while (");
code.startLineWithNum(region.getConditionSourceLine());
code.add("} while (");
conditionGen.add(code, condition);
code.add(");");
} else {
code.startLine("while (");
code.startLineWithNum(region.getConditionSourceLine());
code.add("while (");
conditionGen.add(code, condition);
code.add(") {");
makeRegionIndent(code, region.getBody());
@@ -24,9 +24,9 @@ class DeobfPresets {
private final Deobfuscator deobfuscator;
private final File deobfMapFile;
private final Map<String, String> clsPresetMap = new HashMap<String, String>();
private final Map<String, String> fldPresetMap = new HashMap<String, String>();
private final Map<String, String> mthPresetMap = new HashMap<String, String>();
private final Map<String, String> clsPresetMap = new HashMap<>();
private final Map<String, String> fldPresetMap = new HashMap<>();
private final Map<String, String> mthPresetMap = new HashMap<>();
public DeobfPresets(Deobfuscator deobfuscator, File deobfMapFile) {
this.deobfuscator = deobfuscator;
@@ -98,7 +98,7 @@ class DeobfPresets {
* Saves DefaultDeobfuscator presets
*/
private void dumpMapping() throws IOException {
List<String> list = new ArrayList<String>();
List<String> list = new ArrayList<>();
// packages
for (PackageNode p : deobfuscator.getRootPackage().getInnerPackages()) {
for (PackageNode pp : p.getInnerPackages()) {
@@ -1,6 +1,6 @@
package jadx.core.deobf;
import jadx.api.IJadxArgs;
import jadx.api.JadxArgs;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.SourceFileAttr;
import jadx.core.dex.info.ClassInfo;
@@ -16,7 +16,6 @@ import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -35,20 +34,20 @@ public class Deobfuscator {
public static final String CLASS_NAME_SEPARATOR = ".";
public static final String INNER_CLASS_SEPARATOR = "$";
private final IJadxArgs args;
private final JadxArgs args;
@NotNull
private final List<DexNode> dexNodes;
private final DeobfPresets deobfPresets;
private final Map<ClassInfo, DeobfClsInfo> clsMap = new HashMap<ClassInfo, DeobfClsInfo>();
private final Map<FieldInfo, String> fldMap = new HashMap<FieldInfo, String>();
private final Map<MethodInfo, String> mthMap = new HashMap<MethodInfo, String>();
private final Map<ClassInfo, DeobfClsInfo> clsMap = new HashMap<>();
private final Map<FieldInfo, String> fldMap = new HashMap<>();
private final Map<MethodInfo, String> mthMap = new HashMap<>();
private final Map<MethodInfo, OverridedMethodsNode> ovrdMap = new HashMap<MethodInfo, OverridedMethodsNode>();
private final List<OverridedMethodsNode> ovrd = new ArrayList<OverridedMethodsNode>();
private final Map<MethodInfo, OverridedMethodsNode> ovrdMap = new HashMap<>();
private final List<OverridedMethodsNode> ovrd = new ArrayList<>();
private final PackageNode rootPackage = new PackageNode("");
private final Set<String> pkgSet = new TreeSet<String>();
private final Set<String> pkgSet = new TreeSet<>();
private final int maxLength;
private final int minLength;
@@ -59,13 +58,13 @@ public class Deobfuscator {
private int fldIndex = 0;
private int mthIndex = 0;
public Deobfuscator(IJadxArgs args, @NotNull List<DexNode> dexNodes, File deobfMapFile) {
public Deobfuscator(JadxArgs args, @NotNull List<DexNode> dexNodes, File deobfMapFile) {
this.args = args;
this.dexNodes = dexNodes;
this.minLength = args.getDeobfuscationMinLength();
this.maxLength = args.getDeobfuscationMaxLength();
this.useSourceNameAsAlias = args.useSourceNameAsClassAlias();
this.useSourceNameAsAlias = args.isUseSourceNameAsClassAlias();
this.deobfPresets = new DeobfPresets(this, deobfMapFile);
}
@@ -111,22 +110,23 @@ public class Deobfuscator {
private void postProcess() {
int id = 1;
for (OverridedMethodsNode o : ovrd) {
Iterator<MethodInfo> it = o.getMethods().iterator();
if (it.hasNext()) {
MethodInfo mth = it.next();
if (mth.isRenamed() && !mth.isAliasFromPreset()) {
mth.setAlias(String.format("mo%d%s", id, makeName(mth.getName())));
boolean aliasFromPreset = false;
String aliasToUse = null;
for (MethodInfo mth : o.getMethods()) {
if (mth.isAliasFromPreset()) {
aliasToUse = mth.getAlias();
aliasFromPreset = true;
}
String firstMethodAlias = mth.getAlias();
while (it.hasNext()) {
mth = it.next();
if (!mth.getAlias().equals(firstMethodAlias)) {
mth.setAlias(firstMethodAlias);
}
for (MethodInfo mth : o.getMethods()) {
if (aliasToUse == null) {
if (mth.isRenamed() && !mth.isAliasFromPreset()) {
mth.setAlias(String.format("mo%d%s", id, makeName(mth.getName())));
}
aliasToUse = mth.getAlias();
}
mth.setAlias(aliasToUse);
mth.setAliasFromPreset(aliasFromPreset);
}
id++;
@@ -201,9 +201,8 @@ public class Deobfuscator {
}
private void resolveOverriding(DexNode dex, ClassNode cls, MethodNode mth) {
Set<MethodInfo> overrideSet = new HashSet<MethodInfo>();
Set<MethodInfo> overrideSet = new HashSet<>();
resolveOverridingInternal(dex, cls, mth.getMethodInfo().makeSignature(false), overrideSet, cls);
if (overrideSet.size() > 1) {
OverridedMethodsNode overrideNode = null;
for (MethodInfo _mth : overrideSet) {
@@ -212,12 +211,10 @@ public class Deobfuscator {
break;
}
}
if (overrideNode == null) {
overrideNode = new OverridedMethodsNode(overrideSet);
ovrd.add(overrideNode);
}
for (MethodInfo _mth : overrideSet) {
if (!ovrdMap.containsKey(_mth)) {
ovrdMap.put(_mth, overrideNode);
@@ -228,7 +225,6 @@ public class Deobfuscator {
}
} else {
overrideSet.clear();
overrideSet = null;
}
}
@@ -442,9 +438,9 @@ public class Deobfuscator {
parentPkg = parentPkg.getParentPackage();
}
final String pkgName = pkg.getName();
String pkgName = pkg.getName();
if (!pkg.hasAlias() && shouldRename(pkgName)) {
final String pkgAlias = String.format("p%03d%s", pkgIndex++, makeName(pkgName));
String pkgAlias = String.format("p%03d%s", pkgIndex++, makeName(pkgName));
pkg.setAlias(pkgAlias);
}
}
@@ -501,7 +497,7 @@ public class Deobfuscator {
}
private String getPackageName(String packageName) {
final PackageNode pkg = getPackageNode(packageName, false);
PackageNode pkg = getPackageNode(packageName, false);
if (pkg != null) {
return pkg.getFullAlias();
}
@@ -509,7 +505,7 @@ public class Deobfuscator {
}
private String getClassName(ClassInfo clsInfo) {
final DeobfClsInfo deobfClsInfo = clsMap.get(clsInfo);
DeobfClsInfo deobfClsInfo = clsMap.get(clsInfo);
if (deobfClsInfo != null) {
return deobfClsInfo.makeNameWithoutPkg();
}
@@ -521,7 +517,7 @@ public class Deobfuscator {
}
private String getClassFullName(ClassInfo clsInfo) {
final DeobfClsInfo deobfClsInfo = clsMap.get(clsInfo);
DeobfClsInfo deobfClsInfo = clsMap.get(clsInfo);
if (deobfClsInfo != null) {
return deobfClsInfo.getFullName();
}
@@ -13,8 +13,8 @@ public class NameMapper {
private static final Pattern VALID_JAVA_FULL_IDENTIFIER = Pattern.compile(
"(" + VALID_JAVA_IDENTIFIER + "\\.)*" + VALID_JAVA_IDENTIFIER);
private static final Set<String> RESERVED_NAMES = new HashSet<String>(
Arrays.asList(new String[]{
private static final Set<String> RESERVED_NAMES = new HashSet<>(
Arrays.asList(
"abstract",
"assert",
"boolean",
@@ -67,8 +67,8 @@ public class NameMapper {
"try",
"void",
"volatile",
"while",
})
"while"
)
);
public static boolean isReserved(String str) {
@@ -96,4 +96,7 @@ public class NameMapper {
}
return true;
}
private NameMapper() {
}
}
@@ -1,9 +1,10 @@
package jadx.core.deobf;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Deque;
import java.util.List;
import java.util.Stack;
public class PackageNode {
@@ -29,11 +30,11 @@ public class PackageNode {
public String getFullName() {
if (cachedPackageFullName == null) {
Stack<PackageNode> pp = getParentPackages();
Deque<PackageNode> pp = getParentPackages();
StringBuilder result = new StringBuilder();
result.append(pp.pop().getName());
while (pp.size() > 0) {
while (!pp.isEmpty()) {
result.append(SEPARATOR_CHAR);
result.append(pp.pop().getName());
}
@@ -59,12 +60,12 @@ public class PackageNode {
public String getFullAlias() {
if (cachedPackageFullAlias == null) {
Stack<PackageNode> pp = getParentPackages();
Deque<PackageNode> pp = getParentPackages();
StringBuilder result = new StringBuilder();
if (pp.size() > 0) {
if (!pp.isEmpty()) {
result.append(pp.pop().getAlias());
while (pp.size() > 0) {
while (!pp.isEmpty()) {
result.append(SEPARATOR_CHAR);
result.append(pp.pop().getAlias());
}
@@ -86,7 +87,7 @@ public class PackageNode {
public void addInnerPackage(PackageNode pkg) {
if (innerPackages.isEmpty()) {
innerPackages = new ArrayList<PackageNode>();
innerPackages = new ArrayList<>();
}
innerPackages.add(pkg);
pkg.parentPackage = this;
@@ -114,16 +115,15 @@ public class PackageNode {
*
* @return stack with parent packages
*/
private Stack<PackageNode> getParentPackages() {
Stack<PackageNode> pp = new Stack<PackageNode>();
private Deque<PackageNode> getParentPackages() {
Deque<PackageNode> pp = new ArrayDeque<>();
PackageNode currentP = this;
PackageNode parentP = currentP.getParentPackage();
while (currentP != parentP) {
pp.push(currentP);
currentP = parentP;
parentP = currentP.getParentPackage();
PackageNode currentPkg = this;
PackageNode parentPkg = currentPkg.getParentPackage();
while (currentPkg != parentPkg) {
pp.push(currentPkg);
currentPkg = parentPkg;
parentPkg = currentPkg.getParentPackage();
}
return pp;
}
@@ -29,25 +29,25 @@ import jadx.core.dex.trycatch.SplitterBlockAttr;
*/
public class AType<T extends IAttribute> {
public static final AType<AttrList<JumpInfo>> JUMP = new AType<AttrList<JumpInfo>>();
public static final AType<AttrList<LoopInfo>> LOOP = new AType<AttrList<LoopInfo>>();
public static final AType<AttrList<EdgeInsnAttr>> EDGE_INSN = new AType<AttrList<EdgeInsnAttr>>();
public static final AType<AttrList<JumpInfo>> JUMP = new AType<>();
public static final AType<AttrList<LoopInfo>> LOOP = new AType<>();
public static final AType<AttrList<EdgeInsnAttr>> EDGE_INSN = new AType<>();
public static final AType<ExcHandlerAttr> EXC_HANDLER = new AType<ExcHandlerAttr>();
public static final AType<CatchAttr> CATCH_BLOCK = new AType<CatchAttr>();
public static final AType<SplitterBlockAttr> SPLITTER_BLOCK = new AType<SplitterBlockAttr>();
public static final AType<ForceReturnAttr> FORCE_RETURN = new AType<ForceReturnAttr>();
public static final AType<FieldInitAttr> FIELD_INIT = new AType<FieldInitAttr>();
public static final AType<FieldReplaceAttr> FIELD_REPLACE = new AType<FieldReplaceAttr>();
public static final AType<JadxErrorAttr> JADX_ERROR = new AType<JadxErrorAttr>();
public static final AType<MethodInlineAttr> METHOD_INLINE = new AType<MethodInlineAttr>();
public static final AType<EnumClassAttr> ENUM_CLASS = new AType<EnumClassAttr>();
public static final AType<EnumMapAttr> ENUM_MAP = new AType<EnumMapAttr>();
public static final AType<AnnotationsList> ANNOTATION_LIST = new AType<AnnotationsList>();
public static final AType<MethodParameters> ANNOTATION_MTH_PARAMETERS = new AType<MethodParameters>();
public static final AType<PhiListAttr> PHI_LIST = new AType<PhiListAttr>();
public static final AType<SourceFileAttr> SOURCE_FILE = new AType<SourceFileAttr>();
public static final AType<DeclareVariablesAttr> DECLARE_VARIABLES = new AType<DeclareVariablesAttr>();
public static final AType<LoopLabelAttr> LOOP_LABEL = new AType<LoopLabelAttr>();
public static final AType<IgnoreEdgeAttr> IGNORE_EDGE = new AType<IgnoreEdgeAttr>();
public static final AType<ExcHandlerAttr> EXC_HANDLER = new AType<>();
public static final AType<CatchAttr> CATCH_BLOCK = new AType<>();
public static final AType<SplitterBlockAttr> SPLITTER_BLOCK = new AType<>();
public static final AType<ForceReturnAttr> FORCE_RETURN = new AType<>();
public static final AType<FieldInitAttr> FIELD_INIT = new AType<>();
public static final AType<FieldReplaceAttr> FIELD_REPLACE = new AType<>();
public static final AType<JadxErrorAttr> JADX_ERROR = new AType<>();
public static final AType<MethodInlineAttr> METHOD_INLINE = new AType<>();
public static final AType<EnumClassAttr> ENUM_CLASS = new AType<>();
public static final AType<EnumMapAttr> ENUM_MAP = new AType<>();
public static final AType<AnnotationsList> ANNOTATION_LIST = new AType<>();
public static final AType<MethodParameters> ANNOTATION_MTH_PARAMETERS = new AType<>();
public static final AType<PhiListAttr> PHI_LIST = new AType<>();
public static final AType<SourceFileAttr> SOURCE_FILE = new AType<>();
public static final AType<DeclareVariablesAttr> DECLARE_VARIABLES = new AType<>();
public static final AType<LoopLabelAttr> LOOP_LABEL = new AType<>();
public static final AType<IgnoreEdgeAttr> IGNORE_EDGE = new AType<>();
}
@@ -8,7 +8,7 @@ import java.util.List;
public class AttrList<T> implements IAttribute {
private final AType<AttrList<T>> type;
private final List<T> list = new LinkedList<T>();
private final List<T> list = new LinkedList<>();
public AttrList(AType<AttrList<T>> type) {
this.type = type;
@@ -24,7 +24,7 @@ public class AttributeStorage {
public AttributeStorage() {
flags = EnumSet.noneOf(AFlag.class);
attributes = new IdentityHashMap<AType<?>, IAttribute>();
attributes = new IdentityHashMap<>();
}
public void add(AFlag flag) {
@@ -38,7 +38,7 @@ public class AttributeStorage {
public <T> void add(AType<AttrList<T>> type, T obj) {
AttrList<T> list = get(type);
if (list == null) {
list = new AttrList<T>(type);
list = new AttrList<>(type);
add(list);
}
list.getList().add(obj);
@@ -84,7 +84,7 @@ public class AttributeStorage {
}
public void remove(IAttribute attr) {
AType<?> type = attr.getType();
AType<? extends IAttribute> type = attr.getType();
IAttribute a = attributes.get(type);
if (a == attr) {
attributes.remove(type);
@@ -101,7 +101,7 @@ public class AttributeStorage {
if (size == 0) {
return Collections.emptyList();
}
List<String> list = new ArrayList<String>(size);
List<String> list = new ArrayList<>(size);
for (AFlag a : flags) {
list.add(a.toString());
}
@@ -1,7 +1,5 @@
package jadx.core.dex.attributes;
public interface IAttribute {
AType<?> getType();
AType<? extends IAttribute> getType();
}
@@ -17,7 +17,7 @@ public class AnnotationsList implements IAttribute {
private final Map<String, Annotation> map;
public AnnotationsList(List<Annotation> anList) {
map = new HashMap<String, Annotation>(anList.size());
map = new HashMap<>(anList.size());
for (Annotation a : anList) {
map.put(a.getAnnotationClass(), a);
}
@@ -12,7 +12,7 @@ public class MethodParameters implements IAttribute {
private final List<AnnotationsList> paramList;
public MethodParameters(int paramCount) {
paramList = new ArrayList<AnnotationsList>(paramCount);
paramList = new ArrayList<>(paramCount);
}
public List<AnnotationsList> getParamList() {
@@ -13,7 +13,7 @@ import java.util.List;
*/
public class DeclareVariablesAttr implements IAttribute {
private final List<RegisterArg> vars = new LinkedList<RegisterArg>();
private final List<RegisterArg> vars = new LinkedList<>();
public Iterable<RegisterArg> getVars() {
return vars;
@@ -54,7 +54,7 @@ public class EnumClassAttr implements IAttribute {
private MethodNode staticMethod;
public EnumClassAttr(int fieldsCount) {
this.fields = new ArrayList<EnumField>(fieldsCount);
this.fields = new ArrayList<>(fieldsCount);
}
public List<EnumField> getFields() {
@@ -10,7 +10,7 @@ import java.util.Map;
public class EnumMapAttr implements IAttribute {
public static class KeyValueMap {
private final Map<Object, Object> map = new HashMap<Object, Object>();
private final Map<Object, Object> map = new HashMap<>();
public Object get(Object key) {
return map.get(key);
@@ -21,7 +21,7 @@ public class EnumMapAttr implements IAttribute {
}
}
private final Map<FieldNode, KeyValueMap> fieldsMap = new HashMap<FieldNode, KeyValueMap>();
private final Map<FieldNode, KeyValueMap> fieldsMap = new HashMap<>();
public KeyValueMap getMap(FieldNode field) {
return fieldsMap.get(field);
@@ -10,7 +10,7 @@ import java.util.Set;
public class IgnoreEdgeAttr implements IAttribute {
private final Set<BlockNode> blocks = new HashSet<BlockNode>(3);
private final Set<BlockNode> blocks = new HashSet<>(3);
public Set<BlockNode> getBlocks() {
return blocks;
@@ -43,7 +43,7 @@ public class LoopInfo {
* Exit nodes belongs to loop (contains in {@code loopBlocks})
*/
public Set<BlockNode> getExitNodes() {
Set<BlockNode> nodes = new HashSet<BlockNode>();
Set<BlockNode> nodes = new HashSet<>();
Set<BlockNode> blocks = getLoopBlocks();
for (BlockNode block : blocks) {
// exit: successor node not from this loop, (don't change to getCleanSuccessors)
@@ -60,7 +60,7 @@ public class LoopInfo {
* Return loop exit edges.
*/
public List<Edge> getExitEdges() {
List<Edge> edges = new LinkedList<Edge>();
List<Edge> edges = new LinkedList<>();
Set<BlockNode> blocks = getLoopBlocks();
for (BlockNode block : blocks) {
for (BlockNode s : block.getSuccessors()) {
@@ -9,7 +9,7 @@ import java.util.List;
public class PhiListAttr implements IAttribute {
private final List<PhiInsn> list = new LinkedList<PhiInsn>();
private final List<PhiInsn> list = new LinkedList<>();
@Override
public AType<PhiListAttr> getType() {
@@ -35,12 +35,12 @@ public final class ClassInfo {
if (type.isArray()) {
type = ArgType.OBJECT;
}
ClassInfo cls = dex.getInfoStorage().getCls(type);
ClassInfo cls = dex.root().getInfoStorage().getCls(type);
if (cls != null) {
return cls;
}
cls = new ClassInfo(dex, type);
return dex.getInfoStorage().putCls(cls);
return dex.root().getInfoStorage().putCls(cls);
}
public static ClassInfo fromDex(DexNode dex, int clsIndex) {
@@ -89,6 +89,10 @@ public final class ClassInfo {
int sep = clsName.lastIndexOf('$');
if (canBeInner && sep > 0 && sep != clsName.length() - 1) {
String parClsName = pkg + "." + clsName.substring(0, sep);
if (pkg.isEmpty()) {
parClsName = clsName.substring(0, sep);
}
parentClass = fromName(dex, parClsName);
clsName = clsName.substring(sep + 1);
} else {
@@ -1,15 +1,5 @@
package jadx.core.dex.info;
import jadx.api.IJadxArgs;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.instructions.args.LiteralArg;
import jadx.core.dex.instructions.args.PrimitiveType;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.DexNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.ResRefField;
import jadx.core.dex.nodes.parser.FieldInitAttr;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -18,11 +8,21 @@ import java.util.Set;
import org.jetbrains.annotations.Nullable;
import jadx.api.JadxArgs;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.instructions.args.LiteralArg;
import jadx.core.dex.instructions.args.PrimitiveType;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.DexNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.ResRefField;
import jadx.core.dex.nodes.parser.FieldInitAttr;
public class ConstStorage {
private static final class Values {
private final Map<Object, FieldNode> values = new HashMap<Object, FieldNode>();
private final Set<Object> duplicates = new HashSet<Object>();
private static final class ValueStorage {
private final Map<Object, FieldNode> values = new HashMap<>();
private final Set<Object> duplicates = new HashSet<>();
public Map<Object, FieldNode> getValues() {
return values;
@@ -55,12 +55,12 @@ public class ConstStorage {
}
private final boolean replaceEnabled;
private final Values globalValues = new Values();
private final Map<ClassNode, Values> classes = new HashMap<ClassNode, Values>();
private final ValueStorage globalValues = new ValueStorage();
private final Map<ClassNode, ValueStorage> classes = new HashMap<>();
private Map<Integer, String> resourcesNames = new HashMap<Integer, String>();
private Map<Integer, String> resourcesNames = new HashMap<>();
public ConstStorage(IJadxArgs args) {
public ConstStorage(JadxArgs args) {
this.replaceEnabled = args.isReplaceConsts();
}
@@ -90,10 +90,10 @@ public class ConstStorage {
}
}
private Values getClsValues(ClassNode cls) {
Values classValues = classes.get(cls);
private ValueStorage getClsValues(ClassNode cls) {
ValueStorage classValues = classes.get(cls);
if (classValues == null) {
classValues = new Values();
classValues = new ValueStorage();
classes.put(cls, classValues);
}
return classValues;
@@ -117,7 +117,7 @@ public class ConstStorage {
}
ClassNode current = cls;
while (current != null) {
Values classValues = classes.get(current);
ValueStorage classValues = classes.get(current);
if (classValues != null) {
FieldNode field = classValues.get(value);
if (field != null) {
@@ -161,12 +161,14 @@ public class ConstStorage {
return getConstField(cls, literal, Math.abs(literal) > 1000);
case FLOAT:
float f = Float.intBitsToFloat((int) literal);
return getConstField(cls, f, f != 0.0);
return getConstField(cls, f, Float.compare(f, 0) == 0);
case DOUBLE:
double d = Double.longBitsToDouble(literal);
return getConstField(cls, d, d != 0);
return getConstField(cls, d, Double.compare(d, 0) == 0);
default:
return null;
}
return null;
}
public void setResourcesNames(Map<Integer, String> resourcesNames) {
@@ -22,7 +22,7 @@ public final class FieldInfo {
public static FieldInfo from(DexNode dex, ClassInfo declClass, String name, ArgType type) {
FieldInfo field = new FieldInfo(declClass, name, type);
return dex.getInfoStorage().getField(field);
return dex.root().getInfoStorage().getField(field);
}
public static FieldInfo fromDex(DexNode dex, int index) {
@@ -1,15 +1,16 @@
package jadx.core.dex.info;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.DexNode;
import java.util.HashMap;
import java.util.Map;
public class InfoStorage {
private final Map<ArgType, ClassInfo> classes = new HashMap<ArgType, ClassInfo>();
private final Map<Integer, MethodInfo> methods = new HashMap<Integer, MethodInfo>();
private final Map<FieldInfo, FieldInfo> fields = new HashMap<FieldInfo, FieldInfo>();
private final Map<ArgType, ClassInfo> classes = new HashMap<>();
private final Map<Integer, MethodInfo> methods = new HashMap<>();
private final Map<FieldInfo, FieldInfo> fields = new HashMap<>();
public ClassInfo getCls(ArgType type) {
return classes.get(type);
@@ -22,13 +23,17 @@ public class InfoStorage {
}
}
public MethodInfo getMethod(int mtdId) {
return methods.get(mtdId);
private int generateMethodLookupId(DexNode dex, int mthId) {
return dex.getDexId() << 16 | mthId;
}
public MethodInfo putMethod(int mthId, MethodInfo mth) {
public MethodInfo getMethod(DexNode dex, int mtdId) {
return methods.get(generateMethodLookupId(dex, mtdId));
}
public MethodInfo putMethod(DexNode dex, int mthId, MethodInfo mth) {
synchronized (methods) {
MethodInfo prev = methods.put(mthId, mth);
MethodInfo prev = methods.put(generateMethodLookupId(dex, mthId), mth);
return prev == null ? mth : prev;
}
}
@@ -34,12 +34,12 @@ public final class MethodInfo {
}
public static MethodInfo fromDex(DexNode dex, int mthIndex) {
MethodInfo mth = dex.getInfoStorage().getMethod(mthIndex);
MethodInfo mth = dex.root().getInfoStorage().getMethod(dex, mthIndex);
if (mth != null) {
return mth;
}
mth = new MethodInfo(dex, mthIndex);
return dex.getInfoStorage().putMethod(mthIndex, mth);
return dex.root().getInfoStorage().putMethod(dex, mthIndex, mth);
}
public String makeSignature(boolean includeRetType) {
@@ -66,7 +66,7 @@ public final class FillArrayNode extends InsnNode {
}
public List<LiteralArg> getLiteralArgs() {
List<LiteralArg> list = new ArrayList<LiteralArg>(size);
List<LiteralArg> list = new ArrayList<>(size);
Object array = data;
if (array instanceof int[]) {
for (int b : (int[]) array) {
@@ -1,5 +1,19 @@
package jadx.core.dex.instructions;
import java.io.EOFException;
import com.android.dex.Code;
import com.android.dx.io.OpcodeInfo;
import com.android.dx.io.Opcodes;
import com.android.dx.io.instructions.DecodedInstruction;
import com.android.dx.io.instructions.FillArrayDataPayloadDecodedInstruction;
import com.android.dx.io.instructions.PackedSwitchPayloadDecodedInstruction;
import com.android.dx.io.instructions.ShortArrayCodeInput;
import com.android.dx.io.instructions.SparseSwitchPayloadDecodedInstruction;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.args.ArgType;
@@ -12,16 +26,8 @@ import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.InsnUtils;
import jadx.core.utils.exceptions.DecodeException;
import com.android.dex.Code;
import com.android.dx.io.OpcodeInfo;
import com.android.dx.io.Opcodes;
import com.android.dx.io.instructions.DecodedInstruction;
import com.android.dx.io.instructions.FillArrayDataPayloadDecodedInstruction;
import com.android.dx.io.instructions.PackedSwitchPayloadDecodedInstruction;
import com.android.dx.io.instructions.ShortArrayCodeInput;
import com.android.dx.io.instructions.SparseSwitchPayloadDecodedInstruction;
public class InsnDecoder {
private static final Logger LOG = LoggerFactory.getLogger(InsnDecoder.class);
private final MethodNode method;
private final DexNode dex;
@@ -37,26 +43,36 @@ public class InsnDecoder {
int size = encodedInstructions.length;
DecodedInstruction[] decoded = new DecodedInstruction[size];
ShortArrayCodeInput in = new ShortArrayCodeInput(encodedInstructions);
try {
while (in.hasMore()) {
decoded[in.cursor()] = DecodedInstruction.decode(in);
decoded[in.cursor()] = decodeRawInsn(in);
}
} catch (Exception e) {
throw new DecodeException(method, "", e);
throw new DecodeException(method, e.getMessage(), e);
}
insnArr = decoded;
}
private DecodedInstruction decodeRawInsn(ShortArrayCodeInput in) throws EOFException {
int opcodeUnit = in.read();
int opcode = Opcodes.extractOpcodeFromUnit(opcodeUnit);
OpcodeInfo.Info opcodeInfo;
try {
opcodeInfo = OpcodeInfo.get(opcode);
} catch (IllegalArgumentException e) {
LOG.warn("Ignore decode error: '{}', replace with NOP instruction", e.getMessage());
opcodeInfo = OpcodeInfo.NOP;
}
return opcodeInfo.getFormat().decode(opcodeUnit, in);
}
public InsnNode[] process() throws DecodeException {
InsnNode[] instructions = new InsnNode[insnArr.length];
for (int i = 0; i < insnArr.length; i++) {
DecodedInstruction rawInsn = insnArr[i];
if (rawInsn != null) {
InsnNode insn = decode(rawInsn, i);
if (insn != null) {
insn.setOffset(i);
}
insn.setOffset(i);
instructions[i] = insn;
} else {
instructions[i] = null;
@@ -66,13 +82,14 @@ public class InsnDecoder {
return instructions;
}
@NotNull
private InsnNode decode(DecodedInstruction insn, int offset) throws DecodeException {
switch (insn.getOpcode()) {
case Opcodes.NOP:
case Opcodes.PACKED_SWITCH_PAYLOAD:
case Opcodes.SPARSE_SWITCH_PAYLOAD:
case Opcodes.FILL_ARRAY_DATA_PAYLOAD:
return null;
return new InsnNode(InsnType.NOP, 0);
// move-result will be process in invoke and filled-new-array instructions
case Opcodes.MOVE_RESULT:
@@ -307,6 +324,11 @@ public class InsnDecoder {
case Opcodes.NEG_DOUBLE:
return neg(insn, ArgType.DOUBLE);
case Opcodes.NOT_INT:
return not(insn, ArgType.INT);
case Opcodes.NOT_LONG:
return not(insn, ArgType.LONG);
case Opcodes.INT_TO_BYTE:
return cast(insn, ArgType.INT, ArgType.BYTE);
case Opcodes.INT_TO_CHAR:
@@ -563,9 +585,10 @@ public class InsnDecoder {
return insn(InsnType.MONITOR_EXIT,
null,
InsnArg.reg(insn, 0, ArgType.UNKNOWN_OBJECT));
}
throw new DecodeException("Unknown instruction: " + OpcodeInfo.getName(insn.getOpcode()));
default:
throw new DecodeException("Unknown instruction: '" + OpcodeInfo.getName(insn.getOpcode()) + "'");
}
}
private InsnNode decodeSwitch(DecodedInstruction insn, int offset, boolean packed) {
@@ -681,6 +704,13 @@ public class InsnDecoder {
return inode;
}
private InsnNode not(DecodedInstruction insn, ArgType type) {
InsnNode inode = new InsnNode(InsnType.NOT, 1);
inode.setResult(InsnArg.reg(insn, 0, type));
inode.addArg(InsnArg.reg(insn, 1, type));
return inode;
}
private InsnNode insn(InsnType type, RegisterArg res) {
InsnNode node = new InsnNode(type, 0);
node.setResult(res);
@@ -8,6 +8,7 @@ public enum InsnType {
ARITH,
NEG,
NOT,
MOVE,
CAST,
@@ -21,7 +21,7 @@ public final class PhiInsn extends InsnNode {
public PhiInsn(int regNum, int predecessors) {
super(InsnType.PHI, predecessors);
this.blockBinds = new IdentityHashMap<RegisterArg, BlockNode>(predecessors);
this.blockBinds = new IdentityHashMap<>(predecessors);
setResult(InsnArg.reg(regNum, ArgType.UNKNOWN));
add(AFlag.DONT_INLINE);
}
@@ -140,16 +140,16 @@ public abstract class ArgType {
}
private static class ObjectType extends KnownType {
private final String object;
private final String objName;
public ObjectType(String obj) {
this.object = Utils.cleanObjectName(obj);
this.hash = object.hashCode();
this.objName = Utils.cleanObjectName(obj);
this.hash = objName.hashCode();
}
@Override
public String getObject() {
return object;
return objName;
}
@Override
@@ -164,12 +164,12 @@ public abstract class ArgType {
@Override
boolean internalEquals(Object obj) {
return object.equals(((ObjectType) obj).object);
return objName.equals(((ObjectType) obj).objName);
}
@Override
public String toString() {
return object;
return objName;
}
}
@@ -280,7 +280,7 @@ public abstract class ArgType {
}
private static final class ArrayArg extends KnownType {
public static final PrimitiveType[] ARRAY_POSSIBLES = new PrimitiveType[]{PrimitiveType.ARRAY};
private static final PrimitiveType[] ARRAY_POSSIBLES = new PrimitiveType[]{PrimitiveType.ARRAY};
private final ArgType arrayElement;
public ArrayArg(ArgType arrayElement) {
@@ -495,7 +495,7 @@ public abstract class ArgType {
return null;
} else {
// both types unknown
List<PrimitiveType> types = new ArrayList<PrimitiveType>();
List<PrimitiveType> types = new ArrayList<>();
for (PrimitiveType type : a.getPossibleTypes()) {
if (b.contains(type)) {
types.add(type);
@@ -622,8 +622,10 @@ public abstract class ArgType {
return DOUBLE;
case 'V':
return VOID;
default:
return null;
}
return null;
}
public int getRegCount() {
@@ -57,10 +57,7 @@ public final class FieldArg extends RegisterArg {
if (!field.equals(fieldArg.field)) {
return false;
}
if (instArg != null ? !instArg.equals(fieldArg.instArg) : fieldArg.instArg != null) {
return false;
}
return true;
return instArg != null ? instArg.equals(fieldArg.instArg) : fieldArg.instArg == null;
}
@Override
@@ -101,7 +101,7 @@ public abstract class InsnArg extends Typed {
}
public static void updateParentInsn(InsnNode fromInsn, InsnNode toInsn) {
List<RegisterArg> args = new ArrayList<RegisterArg>();
List<RegisterArg> args = new ArrayList<>();
fromInsn.getRegisterArgs(args);
for (RegisterArg reg : args) {
reg.setParentInsn(toInsn);
@@ -6,12 +6,11 @@ import jadx.core.dex.nodes.DexNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.utils.InsnUtils;
import java.util.Objects;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class RegisterArg extends InsnArg implements Named {
private static final Logger LOG = LoggerFactory.getLogger(RegisterArg.class);
protected final int regNum;
// not null after SSATransform pass
@@ -151,23 +150,13 @@ public class RegisterArg extends InsnArg implements Named {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof RegisterArg)) {
return false;
}
RegisterArg other = (RegisterArg) obj;
if (regNum != other.regNum) {
return false;
}
if (!type.equals(other.type)) {
return false;
}
if (sVar != null && !sVar.equals(other.getSVar())) {
return false;
}
return true;
return regNum == other.regNum
&& type.equals(other.type)
&& Objects.equals(sVar, other.getSVar());
}
@Override
@@ -20,7 +20,7 @@ public class SSAVar extends AttrNode {
@NotNull
private RegisterArg assign;
private final List<RegisterArg> useList = new ArrayList<RegisterArg>(2);
private final List<RegisterArg> useList = new ArrayList<>(2);
@Nullable
private PhiInsn usedInPhi;
@@ -19,10 +19,10 @@ public class BlockNode extends AttrNode implements IBlock {
private int id;
private final int startOffset;
private final List<InsnNode> instructions = new ArrayList<InsnNode>(2);
private final List<InsnNode> instructions = new ArrayList<>(2);
private List<BlockNode> predecessors = new ArrayList<BlockNode>(1);
private List<BlockNode> successors = new ArrayList<BlockNode>(1);
private List<BlockNode> predecessors = new ArrayList<>(1);
private List<BlockNode> successors = new ArrayList<>(1);
private List<BlockNode> cleanSuccessors;
// all dominators
@@ -85,7 +85,7 @@ public class BlockNode extends AttrNode implements IBlock {
if (sucList.isEmpty()) {
return sucList;
}
List<BlockNode> toRemove = new LinkedList<BlockNode>();
List<BlockNode> toRemove = new LinkedList<>();
for (BlockNode b : sucList) {
if (BlockUtils.isBlockMustBeCleared(b)) {
toRemove.add(b);
@@ -104,7 +104,7 @@ public class BlockNode extends AttrNode implements IBlock {
if (toRemove.isEmpty()) {
return sucList;
}
List<BlockNode> result = new ArrayList<BlockNode>(sucList);
List<BlockNode> result = new ArrayList<>(sucList);
result.removeAll(toRemove);
return result;
}
@@ -161,7 +161,7 @@ public class BlockNode extends AttrNode implements IBlock {
public void addDominatesOn(BlockNode block) {
if (dominatesOn.isEmpty()) {
dominatesOn = new LinkedList<BlockNode>();
dominatesOn = new LinkedList<>();
}
dominatesOn.add(block);
}
@@ -60,12 +60,12 @@ public class ClassNode extends LineAttrNode implements ILoadable, IDexNode {
private ClassNode parentClass;
private ProcessState state = ProcessState.NOT_LOADED;
private final Set<ClassNode> dependencies = new HashSet<ClassNode>();
private final Set<ClassNode> dependencies = new HashSet<>();
// cache maps
private Map<MethodInfo, MethodNode> mthInfoMap = Collections.emptyMap();
public ClassNode(DexNode dex, ClassDef cls) throws DecodeException {
public ClassNode(DexNode dex, ClassDef cls) {
this.dex = dex;
this.clsInfo = ClassInfo.fromDex(dex, cls.getTypeIndex());
try {
@@ -74,7 +74,7 @@ public class ClassNode extends LineAttrNode implements ILoadable, IDexNode {
} else {
this.superClass = dex.getType(cls.getSupertypeIndex());
}
this.interfaces = new ArrayList<ArgType>(cls.getInterfaces().length);
this.interfaces = new ArrayList<>(cls.getInterfaces().length);
for (short interfaceIdx : cls.getInterfaces()) {
this.interfaces.add(dex.getType(interfaceIdx));
}
@@ -83,8 +83,8 @@ public class ClassNode extends LineAttrNode implements ILoadable, IDexNode {
int mthsCount = clsData.getDirectMethods().length + clsData.getVirtualMethods().length;
int fieldsCount = clsData.getStaticFields().length + clsData.getInstanceFields().length;
methods = new ArrayList<MethodNode>(mthsCount);
fields = new ArrayList<FieldNode>(fieldsCount);
methods = new ArrayList<>(mthsCount);
fields = new ArrayList<>(fieldsCount);
for (Method mth : clsData.getDirectMethods()) {
methods.add(new MethodNode(this, mth, false));
@@ -128,7 +128,7 @@ public class ClassNode extends LineAttrNode implements ILoadable, IDexNode {
buildCache();
} catch (Exception e) {
throw new DecodeException("Error decode class: " + clsInfo, e);
throw new JadxRuntimeException("Error decode class: " + clsInfo, e);
}
}
@@ -268,7 +268,7 @@ public class ClassNode extends LineAttrNode implements ILoadable, IDexNode {
}
private void buildCache() {
mthInfoMap = new HashMap<MethodInfo, MethodNode>(methods.size());
mthInfoMap = new HashMap<>(methods.size());
for (MethodNode mth : methods) {
mthInfoMap.put(mth.getMethodInfo(), mth);
}
@@ -373,7 +373,7 @@ public class ClassNode extends LineAttrNode implements ILoadable, IDexNode {
public void addInnerClass(ClassNode cls) {
if (innerClasses.isEmpty()) {
innerClasses = new ArrayList<ClassNode>(3);
innerClasses = new ArrayList<>(3);
}
innerClasses.add(cls);
}
@@ -1,22 +1,11 @@
package jadx.core.dex.nodes;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.info.InfoStorage;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.files.DexFile;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import com.android.dex.ClassData;
import com.android.dex.ClassData.Method;
import com.android.dex.ClassDef;
@@ -27,6 +16,14 @@ import com.android.dex.FieldId;
import com.android.dex.MethodId;
import com.android.dex.ProtoId;
import com.android.dex.TypeList;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.utils.files.DexFile;
public class DexNode implements IDexNode {
@@ -35,19 +32,19 @@ public class DexNode implements IDexNode {
private final RootNode root;
private final Dex dexBuf;
private final DexFile file;
private final int dexId;
private final List<ClassNode> classes = new ArrayList<ClassNode>();
private final Map<ClassInfo, ClassNode> clsMap = new HashMap<ClassInfo, ClassNode>();
private final List<ClassNode> classes = new ArrayList<>();
private final Map<ClassInfo, ClassNode> clsMap = new HashMap<>();
private final InfoStorage infoStorage = new InfoStorage();
public DexNode(RootNode root, DexFile input) {
public DexNode(RootNode root, DexFile input, int dexId) {
this.root = root;
this.file = input;
this.dexBuf = input.getDexBuf();
this.dexId = dexId;
}
public void loadClasses() throws DecodeException {
public void loadClasses() {
for (ClassDef cls : dexBuf.classDefs()) {
ClassNode clsNode = new ClassNode(this, cls);
classes.add(clsNode);
@@ -57,7 +54,7 @@ public class DexNode implements IDexNode {
void initInnerClasses() {
// move inner classes
List<ClassNode> inner = new ArrayList<ClassNode>();
List<ClassNode> inner = new ArrayList<>();
for (ClassNode cls : classes) {
if (cls.getClassInfo().isInner()) {
inner.add(cls);
@@ -153,10 +150,6 @@ public class DexNode implements IDexNode {
return null;
}
public InfoStorage getInfoStorage() {
return infoStorage;
}
public DexFile getDexFile() {
return file;
}
@@ -189,7 +182,7 @@ public class DexNode implements IDexNode {
public List<ArgType> readParamList(int parametersOffset) {
TypeList paramList = dexBuf.readTypeList(parametersOffset);
List<ArgType> args = new ArrayList<ArgType>(paramList.getTypes().length);
List<ArgType> args = new ArrayList<>(paramList.getTypes().length);
for (short t : paramList.getTypes()) {
args.add(getType(t));
}
@@ -214,6 +207,10 @@ public class DexNode implements IDexNode {
return this;
}
public int getDexId() {
return dexId;
}
@Override
public String toString() {
return "DEX";
@@ -6,8 +6,6 @@ public interface ILoadable {
/**
* On demand loading
*
* @throws DecodeException
*/
void load() throws DecodeException;
@@ -15,5 +13,4 @@ public interface ILoadable {
* Free resources
*/
void unload();
}
@@ -42,7 +42,7 @@ public class InsnNode extends LineAttrNode {
if (argsCount == 0) {
this.arguments = Collections.emptyList();
} else {
this.arguments = new ArrayList<InsnArg>(argsCount);
this.arguments = new ArrayList<>(argsCount);
}
}
@@ -113,12 +113,12 @@ public class MethodNode extends LineAttrNode implements ILoadable, IDexNode {
load();
noCode = false;
}
throw new DecodeException(this, "Load method exception", e);
throw new DecodeException(this, "Load method exception: " + e.getMessage(), e);
}
}
public void checkInstructions() {
List<RegisterArg> list = new ArrayList<RegisterArg>();
List<RegisterArg> list = new ArrayList<>();
for (InsnNode insnNode : instructions) {
if (insnNode == null) {
continue;
@@ -216,7 +216,7 @@ public class MethodNode extends LineAttrNode implements ILoadable, IDexNode {
argsList = Collections.emptyList();
return;
}
argsList = new ArrayList<RegisterArg>(args.size());
argsList = new ArrayList<>(args.size());
for (ArgType arg : args) {
argsList.add(InsnArg.typeImmutableReg(pos, arg));
pos += arg.getRegCount();
@@ -225,7 +225,7 @@ public class MethodNode extends LineAttrNode implements ILoadable, IDexNode {
public List<RegisterArg> getArguments(boolean includeThis) {
if (includeThis && thisArg != null) {
List<RegisterArg> list = new ArrayList<RegisterArg>(argsList.size() + 1);
List<RegisterArg> list = new ArrayList<>(argsList.size() + 1);
list.add(thisArg);
list.addAll(argsList);
return list;
@@ -259,8 +259,8 @@ public class MethodNode extends LineAttrNode implements ILoadable, IDexNode {
}
int hc = 0;
Set<Integer> addrs = new HashSet<Integer>();
List<TryCatchBlock> catches = new ArrayList<TryCatchBlock>(catchBlocks.length);
Set<Integer> addrs = new HashSet<>();
List<TryCatchBlock> catches = new ArrayList<>(catchBlocks.length);
for (CatchHandler handler : catchBlocks) {
TryCatchBlock tcBlock = new TryCatchBlock();
@@ -399,8 +399,8 @@ public class MethodNode extends LineAttrNode implements ILoadable, IDexNode {
}
public void initBasicBlocks() {
blocks = new ArrayList<BlockNode>();
exitBlocks = new ArrayList<BlockNode>(1);
blocks = new ArrayList<>();
exitBlocks = new ArrayList<>(1);
}
public void finishBasicBlocks() {
@@ -437,7 +437,7 @@ public class MethodNode extends LineAttrNode implements ILoadable, IDexNode {
public void registerLoop(LoopInfo loop) {
if (loops.isEmpty()) {
loops = new ArrayList<LoopInfo>(5);
loops = new ArrayList<>(5);
}
loop.setId(loops.size());
loops.add(loop);
@@ -460,7 +460,7 @@ public class MethodNode extends LineAttrNode implements ILoadable, IDexNode {
if (loops.isEmpty()) {
return Collections.emptyList();
}
List<LoopInfo> list = new ArrayList<LoopInfo>(loops.size());
List<LoopInfo> list = new ArrayList<>(loops.size());
for (LoopInfo loop : loops) {
if (loop.getLoopBlocks().contains(block)) {
list.add(loop);
@@ -479,7 +479,7 @@ public class MethodNode extends LineAttrNode implements ILoadable, IDexNode {
public ExceptionHandler addExceptionHandler(ExceptionHandler handler) {
if (exceptionHandlers.isEmpty()) {
exceptionHandlers = new ArrayList<ExceptionHandler>(2);
exceptionHandlers = new ArrayList<>(2);
} else {
for (ExceptionHandler h : exceptionHandlers) {
if (h == handler || h.getHandleOffset() == handler.getHandleOffset()) {
@@ -528,7 +528,7 @@ public class MethodNode extends LineAttrNode implements ILoadable, IDexNode {
boolean result = false;
if (accFlags.isConstructor() && mthInfo.isConstructor()) {
int defaultArgCount = 0;
/** workaround for non-static inner class constructor, that has synthetic argument */
// workaround for non-static inner class constructor, that has synthetic argument
if (parentClass.getClassInfo().isInner()
&& !parentClass.getAccessFlags().isStatic()) {
ClassNode outerCls = parentClass.getParentClass();
@@ -557,7 +557,7 @@ public class MethodNode extends LineAttrNode implements ILoadable, IDexNode {
public SSAVar makeNewSVar(int regNum, int version, @NotNull RegisterArg assignArg) {
SSAVar var = new SSAVar(regNum, version, assignArg);
if (sVars.isEmpty()) {
sVars = new ArrayList<SSAVar>();
sVars = new ArrayList<>();
}
sVars.add(var);
return var;
@@ -1,23 +1,5 @@
package jadx.core.dex.nodes;
import jadx.api.IJadxArgs;
import jadx.api.ResourceFile;
import jadx.api.ResourceType;
import jadx.api.ResourcesLoader;
import jadx.core.clsp.ClspGraph;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.ConstStorage;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.StringUtils;
import jadx.core.utils.android.AndroidResourcesUtils;
import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.files.DexFile;
import jadx.core.utils.files.InputFile;
import jadx.core.xmlgen.ResContainer;
import jadx.core.xmlgen.ResTableParser;
import jadx.core.xmlgen.ResourceStorage;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
@@ -27,13 +9,33 @@ import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.JadxArgs;
import jadx.api.ResourceFile;
import jadx.api.ResourceType;
import jadx.api.ResourcesLoader;
import jadx.core.clsp.ClspGraph;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.ConstStorage;
import jadx.core.dex.info.InfoStorage;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.StringUtils;
import jadx.core.utils.android.AndroidResourcesUtils;
import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.utils.files.DexFile;
import jadx.core.utils.files.InputFile;
import jadx.core.xmlgen.ResContainer;
import jadx.core.xmlgen.ResTableParser;
import jadx.core.xmlgen.ResourceStorage;
public class RootNode {
private static final Logger LOG = LoggerFactory.getLogger(RootNode.class);
private final ErrorsCounter errorsCounter = new ErrorsCounter();
private final IJadxArgs args;
private final JadxArgs args;
private final StringUtils stringUtils;
private final ConstStorage constValues;
private final InfoStorage infoStorage = new InfoStorage();
private List<DexNode> dexNodes;
@Nullable
@@ -41,22 +43,22 @@ public class RootNode {
private ClassNode appResClass;
private ClspGraph clsp;
public RootNode(IJadxArgs args) {
public RootNode(JadxArgs args) {
this.args = args;
this.stringUtils = new StringUtils(args);
this.constValues = new ConstStorage(args);
}
public void load(List<InputFile> inputFiles) throws DecodeException {
dexNodes = new ArrayList<DexNode>();
public void load(List<InputFile> inputFiles) {
dexNodes = new ArrayList<>();
for (InputFile input : inputFiles) {
for (DexFile dexFile : input.getDexFiles()) {
try {
LOG.debug("Load: {}", dexFile);
DexNode dexNode = new DexNode(this, dexFile);
DexNode dexNode = new DexNode(this, dexFile, dexNodes.size());
dexNodes.add(dexNode);
} catch (Exception e) {
throw new DecodeException("Error decode file: " + dexFile, e);
throw new JadxRuntimeException("Error decode file: " + dexFile, e);
}
}
}
@@ -78,7 +80,7 @@ public class RootNode {
LOG.debug("'.arsc' file not found");
return;
}
final ResTableParser parser = new ResTableParser();
ResTableParser parser = new ResTableParser();
try {
ResourcesLoader.decodeStream(arsc, new ResourcesLoader.ResourceDecoder() {
@Override
@@ -101,22 +103,22 @@ public class RootNode {
appResClass = AndroidResourcesUtils.searchAppResClass(this);
}
public void initClassPath() throws DecodeException {
public void initClassPath() {
try {
if (this.clsp == null) {
ClspGraph clsp = new ClspGraph();
clsp.load();
ClspGraph newClsp = new ClspGraph();
newClsp.load();
List<ClassNode> classes = new ArrayList<ClassNode>();
List<ClassNode> classes = new ArrayList<>();
for (DexNode dexNode : dexNodes) {
classes.addAll(dexNode.getClasses());
}
clsp.addApp(classes);
newClsp.addApp(classes);
this.clsp = clsp;
this.clsp = newClsp;
}
} catch (IOException e) {
throw new DecodeException("Error loading classpath", e);
} catch (Exception e) {
throw new JadxRuntimeException("Error loading classpath", e);
}
}
@@ -127,7 +129,7 @@ public class RootNode {
}
public List<ClassNode> getClasses(boolean includeInner) {
List<ClassNode> classes = new ArrayList<ClassNode>();
List<ClassNode> classes = new ArrayList<>();
for (DexNode dex : dexNodes) {
if (includeInner) {
classes.addAll(dex.getClasses());
@@ -154,7 +156,7 @@ public class RootNode {
}
public List<ClassNode> searchClassByShortName(String shortName) {
List<ClassNode> list = new ArrayList<ClassNode>();
List<ClassNode> list = new ArrayList<>();
for (DexNode dexNode : dexNodes) {
for (ClassNode cls : dexNode.getClasses()) {
if (cls.getClassInfo().getShortName().equals(shortName)) {
@@ -186,10 +188,6 @@ public class RootNode {
return appResClass;
}
public IJadxArgs getArgs() {
return args;
}
public StringUtils getStringUtils() {
return stringUtils;
}
@@ -197,4 +195,12 @@ public class RootNode {
public ConstStorage getConstValues() {
return constValues;
}
public InfoStorage getInfoStorage() {
return infoStorage;
}
public JadxArgs getArgs() {
return args;
}
}
@@ -79,7 +79,7 @@ public class AnnotationsParser {
if (size == 0) {
return AnnotationsList.EMPTY;
}
List<Annotation> list = new ArrayList<Annotation>(size);
List<Annotation> list = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
Section anSection = dex.openSection(section.readInt());
Annotation a = readAnnotation(dex, anSection, true);
@@ -97,7 +97,7 @@ public class AnnotationsParser {
}
int typeIndex = s.readUleb128();
int size = s.readUleb128();
Map<String, Object> values = new LinkedHashMap<String, Object>(size);
Map<String, Object> values = new LinkedHashMap<>(size);
for (int i = 0; i < size; i++) {
String name = dex.getString(s.readUleb128());
values.put(name, parser.parseValue());
@@ -1,5 +1,9 @@
package jadx.core.dex.nodes.parser;
import java.util.List;
import com.android.dex.Dex.Section;
import jadx.core.dex.attributes.nodes.SourceFileAttr;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.RegisterArg;
@@ -9,16 +13,8 @@ import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.exceptions.DecodeException;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.android.dex.Dex.Section;
public class DebugInfoParser {
private static final Logger LOG = LoggerFactory.getLogger(DebugInfoParser.class);
private static final int DBG_END_SEQUENCE = 0x00;
private static final int DBG_ADVANCE_PC = 0x01;
private static final int DBG_ADVANCE_LINE = 0x02;
@@ -1,16 +1,16 @@
package jadx.core.dex.nodes.parser;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.nodes.DexNode;
import jadx.core.utils.exceptions.DecodeException;
import java.util.ArrayList;
import java.util.List;
import com.android.dex.Dex.Section;
import com.android.dex.Leb128;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.nodes.DexNode;
import jadx.core.utils.exceptions.DecodeException;
public class EncValueParser {
private static final int ENCODED_BYTE = 0x00;
@@ -83,7 +83,7 @@ public class EncValueParser {
case ENCODED_ARRAY:
int count = Leb128.readUnsignedLeb128(in);
List<Object> values = new ArrayList<Object>(count);
List<Object> values = new ArrayList<>(count);
for (int i = 0; i < count; i++) {
values.add(parseValue());
}
@@ -91,8 +91,10 @@ public class EncValueParser {
case ENCODED_ANNOTATION:
return AnnotationsParser.readAnnotation(dex, in, false);
default:
throw new DecodeException("Unknown encoded value type: 0x" + Integer.toHexString(type));
}
throw new DecodeException("Unknown encoded value type: 0x" + Integer.toHexString(type));
}
private int parseUnsignedInt(int byteCount) {
@@ -189,7 +189,7 @@ public class SignatureParser {
}
private ArgType[] consumeGenericArgs() {
List<ArgType> list = new LinkedList<ArgType>();
List<ArgType> list = new LinkedList<>();
ArgType type;
do {
if (lookAhead('*')) {
@@ -220,7 +220,7 @@ public class SignatureParser {
if (!lookAhead('<')) {
return Collections.emptyMap();
}
Map<ArgType, List<ArgType>> map = new LinkedHashMap<ArgType, List<ArgType>>(2);
Map<ArgType, List<ArgType>> map = new LinkedHashMap<>(2);
consume('<');
while (true) {
if (lookAhead('>') || next() == STOP_CHAR) {
@@ -251,7 +251,7 @@ public class SignatureParser {
ArgType argType = consumeType();
if (!argType.equals(ArgType.OBJECT)) {
if (types.isEmpty()) {
types = new LinkedList<ArgType>();
types = new LinkedList<>();
}
types.add(argType);
}
@@ -269,7 +269,7 @@ public class SignatureParser {
consume(')');
return Collections.emptyList();
}
List<ArgType> args = new LinkedList<ArgType>();
List<ArgType> args = new LinkedList<>();
do {
args.add(consumeType());
} while (!lookAhead(')'));
@@ -12,7 +12,7 @@ public final class Region extends AbstractRegion {
public Region(IRegion parent) {
super(parent);
this.blocks = new ArrayList<IContainer>(1);
this.blocks = new ArrayList<>(1);
}
@Override
@@ -20,8 +20,8 @@ public final class SwitchRegion extends AbstractRegion implements IBranchRegion
public SwitchRegion(IRegion parent, BlockNode header) {
super(parent);
this.header = header;
this.keys = new ArrayList<List<Object>>();
this.cases = new ArrayList<IContainer>();
this.keys = new ArrayList<>();
this.cases = new ArrayList<>();
}
public BlockNode getHeader() {
@@ -51,7 +51,7 @@ public final class SwitchRegion extends AbstractRegion implements IBranchRegion
@Override
public List<IContainer> getSubBlocks() {
List<IContainer> all = new ArrayList<IContainer>(cases.size() + 2);
List<IContainer> all = new ArrayList<>(cases.size() + 2);
all.add(header);
all.addAll(cases);
if (defCase != null) {
@@ -62,7 +62,7 @@ public final class SwitchRegion extends AbstractRegion implements IBranchRegion
@Override
public List<IContainer> getBranches() {
List<IContainer> branches = new ArrayList<IContainer>(cases.size() + 1);
List<IContainer> branches = new ArrayList<>(cases.size() + 1);
branches.addAll(cases);
branches.add(defCase);
return Collections.unmodifiableList(branches);
@@ -10,7 +10,7 @@ import java.util.List;
public final class SynchronizedRegion extends AbstractRegion {
private final InsnNode enterInsn;
private final List<InsnNode> exitInsns = new LinkedList<InsnNode>();
private final List<InsnNode> exitInsns = new LinkedList<>();
private final Region region;
public SynchronizedRegion(IRegion parent, InsnNode insn) {
@@ -28,7 +28,7 @@ public final class TryCatchRegion extends AbstractRegion implements IBranchRegio
public void setTryCatchBlock(TryCatchBlock tryCatchBlock) {
this.tryCatchBlock = tryCatchBlock;
int count = tryCatchBlock.getHandlersCount();
this.catchRegions = new LinkedHashMap<ExceptionHandler, IContainer>(count);
this.catchRegions = new LinkedHashMap<>(count);
for (ExceptionHandler handler : tryCatchBlock.getHandlers()) {
IContainer handlerRegion = handler.getHandlerRegion();
if (handlerRegion != null) {
@@ -63,7 +63,7 @@ public final class TryCatchRegion extends AbstractRegion implements IBranchRegio
@Override
public List<IContainer> getSubBlocks() {
List<IContainer> all = new ArrayList<IContainer>(2 + catchRegions.size());
List<IContainer> all = new ArrayList<>(2 + catchRegions.size());
all.add(tryRegion);
all.addAll(catchRegions.values());
if (finallyRegion != null) {
@@ -49,7 +49,7 @@ public final class IfCondition {
if (c.mode == Mode.COMPARE) {
this.args = Collections.emptyList();
} else {
this.args = new ArrayList<IfCondition>(c.args);
this.args = new ArrayList<>(c.args);
}
}
@@ -121,7 +121,7 @@ public final class IfCondition {
case AND:
case OR:
List<IfCondition> args = cond.getArgs();
List<IfCondition> newArgs = new ArrayList<IfCondition>(args.size());
List<IfCondition> newArgs = new ArrayList<>(args.size());
for (IfCondition arg : args) {
newArgs.add(invert(arg));
}
@@ -154,7 +154,7 @@ public final class IfCondition {
IfCondition simpl = simplify(arg);
if (simpl != arg) {
if (args == null) {
args = new ArrayList<IfCondition>(cond.getArgs());
args = new ArrayList<>(cond.getArgs());
}
args.set(i, simpl);
}
@@ -206,7 +206,7 @@ public final class IfCondition {
}
public List<RegisterArg> getRegisterArgs() {
List<RegisterArg> list = new LinkedList<RegisterArg>();
List<RegisterArg> list = new LinkedList<>();
if (mode == Mode.COMPARE) {
compare.getInsn().getRegisterArgs(list);
} else {
@@ -82,7 +82,7 @@ public final class IfRegion extends AbstractRegion implements IBranchRegion {
@Override
public List<IContainer> getSubBlocks() {
List<IContainer> all = new ArrayList<IContainer>(3);
List<IContainer> all = new ArrayList<>(3);
all.add(header);
if (thenRegion != null) {
all.add(thenRegion);
@@ -95,7 +95,7 @@ public final class IfRegion extends AbstractRegion implements IBranchRegion {
@Override
public List<IContainer> getBranches() {
List<IContainer> branches = new ArrayList<IContainer>(2);
List<IContainer> branches = new ArrayList<>(2);
branches.add(thenRegion);
branches.add(elseRegion);
return Collections.unmodifiableList(branches);
@@ -1,5 +1,9 @@
package jadx.core.dex.regions.loops;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import jadx.core.dex.attributes.nodes.LoopInfo;
import jadx.core.dex.instructions.IfNode;
import jadx.core.dex.instructions.args.RegisterArg;
@@ -9,15 +13,15 @@ import jadx.core.dex.nodes.IRegion;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.regions.AbstractRegion;
import jadx.core.dex.regions.conditions.IfCondition;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.jetbrains.annotations.Nullable;
public final class LoopRegion extends AbstractRegion {
private final LoopInfo info;
// loop header contains one 'if' insn, equals null for infinite loop
/**
* loop header contains one 'if' insn, equals null for infinite loop
*/
@Nullable
private IfCondition condition;
private final BlockNode conditionBlock;
// instruction which must be executed before condition in every loop
@@ -27,7 +31,7 @@ public final class LoopRegion extends AbstractRegion {
private LoopType type;
public LoopRegion(IRegion parent, LoopInfo info, BlockNode header, boolean reversed) {
public LoopRegion(IRegion parent, LoopInfo info, @Nullable BlockNode header, boolean reversed) {
super(parent);
this.info = info;
this.conditionBlock = header;
@@ -126,6 +130,16 @@ public final class LoopRegion extends AbstractRegion {
}
}
public int getConditionSourceLine() {
if (conditionBlock != null) {
List<InsnNode> condInsns = conditionBlock.getInstructions();
if (!condInsns.isEmpty()) {
return condInsns.get(0).getSourceLine();
}
}
return 0;
}
public LoopType getType() {
return type;
}
@@ -136,7 +150,7 @@ public final class LoopRegion extends AbstractRegion {
@Override
public List<IContainer> getSubBlocks() {
List<IContainer> all = new ArrayList<IContainer>(3);
List<IContainer> all = new ArrayList<>(3);
if (preCondition != null) {
all.add(preCondition);
}
@@ -16,7 +16,7 @@ public class ExceptionHandler {
private final int handleOffset;
private BlockNode handlerBlock;
private final List<BlockNode> blocks = new ArrayList<BlockNode>();
private final List<BlockNode> blocks = new ArrayList<>();
private IContainer handlerRegion;
private InsnArg arg;
@@ -23,8 +23,8 @@ public class TryCatchBlock {
private final CatchAttr attr;
public TryCatchBlock() {
handlers = new LinkedList<ExceptionHandler>();
insns = new ArrayList<InsnNode>();
handlers = new LinkedList<>();
insns = new ArrayList<>();
attr = new CatchAttr(this);
}
@@ -68,10 +68,9 @@ public class TryCatchBlock {
BlockUtils.skipPredSyntheticPaths(block);
block.add(AFlag.SKIP);
ExcHandlerAttr excHandlerAttr = block.get(AType.EXC_HANDLER);
if (excHandlerAttr != null) {
if (excHandlerAttr.getHandler().equals(handler)) {
block.remove(AType.EXC_HANDLER);
}
if (excHandlerAttr != null
&& excHandlerAttr.getHandler().equals(handler)) {
block.remove(AType.EXC_HANDLER);
}
SplitterBlockAttr splitter = handler.getHandlerBlock().get(AType.SPLITTER_BLOCK);
if (splitter != null) {
@@ -9,15 +9,17 @@ public class AbstractVisitor implements IDexTreeVisitor {
@Override
public void init(RootNode root) throws JadxException {
// no op implementation
}
@Override
public boolean visit(ClassNode cls) throws JadxException {
// no op implementation
return true;
}
@Override
public void visit(MethodNode mth) throws JadxException {
// no op implementation
}
}
@@ -57,7 +57,7 @@ public class CodeShrinker extends AbstractVisitor {
}
public static List<RegisterArg> getArgs(InsnNode insn) {
List<RegisterArg> args = new LinkedList<RegisterArg>();
List<RegisterArg> args = new LinkedList<>();
addArgs(insn, args);
return args;
}
@@ -191,11 +191,11 @@ public class CodeShrinker extends AbstractVisitor {
}
InsnList insnList = new InsnList(block.getInstructions());
int insnCount = insnList.size();
List<ArgsInfo> argsList = new ArrayList<ArgsInfo>(insnCount);
List<ArgsInfo> argsList = new ArrayList<>(insnCount);
for (int i = 0; i < insnCount; i++) {
argsList.add(new ArgsInfo(insnList.get(i), argsList, i));
}
List<WrapInfo> wrapList = new ArrayList<WrapInfo>();
List<WrapInfo> wrapList = new ArrayList<>();
for (ArgsInfo argsInfo : argsList) {
List<RegisterArg> args = argsInfo.getArgs();
if (args.isEmpty()) {
@@ -31,7 +31,7 @@ public class ConstInlineVisitor extends AbstractVisitor {
if (mth.isNoCode()) {
return;
}
List<InsnNode> toRemove = new ArrayList<InsnNode>();
List<InsnNode> toRemove = new ArrayList<>();
for (BlockNode block : mth.getBasicBlocks()) {
toRemove.clear();
for (InsnNode insn : block.getInstructions()) {
@@ -99,7 +99,7 @@ public class ConstInlineVisitor extends AbstractVisitor {
private static boolean replaceConst(MethodNode mth, InsnNode constInsn, long literal) {
SSAVar sVar = constInsn.getResult().getSVar();
List<RegisterArg> use = new ArrayList<RegisterArg>(sVar.getUseList());
List<RegisterArg> use = new ArrayList<>(sVar.getUseList());
int replaceCount = 0;
for (RegisterArg arg : use) {
InsnNode useInsn = arg.getParentInsn();
@@ -7,40 +7,53 @@ import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.parser.DebugInfoParser;
import jadx.core.utils.BlockUtils;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.exceptions.JadxException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class DebugInfoVisitor extends AbstractVisitor {
private static final Logger LOG = LoggerFactory.getLogger(DebugInfoVisitor.class);
@Override
public void visit(MethodNode mth) throws JadxException {
int debugOffset = mth.getDebugInfoOffset();
if (debugOffset > 0) {
InsnNode[] insnArr = mth.getInstructions();
DebugInfoParser debugInfoParser = new DebugInfoParser(mth, debugOffset, insnArr);
debugInfoParser.process();
// set method source line from first instruction
if (insnArr.length != 0) {
for (InsnNode insn : insnArr) {
if (insn != null) {
int line = insn.getSourceLine();
if (line != 0) {
mth.setSourceLine(line - 1);
}
break;
}
}
try {
int debugOffset = mth.getDebugInfoOffset();
if (debugOffset > 0) {
processDebugInfo(mth, debugOffset);
}
if (!mth.getReturnType().equals(ArgType.VOID)) {
// fix debug info for splitter 'return' instructions
for (BlockNode exit : mth.getExitBlocks()) {
InsnNode ret = BlockUtils.getLastInsn(exit);
if (ret == null) {
continue;
}
InsnNode oldRet = insnArr[ret.getOffset()];
if (oldRet == ret) {
continue;
}
} catch (Exception e) {
LOG.error("Error in debug info parser: {}", ErrorsCounter.formatErrorMsg(mth, e.getMessage()), e);
} finally {
mth.unloadInsnArr();
}
}
private void processDebugInfo(MethodNode mth, int debugOffset) throws DecodeException {
InsnNode[] insnArr = mth.getInstructions();
DebugInfoParser debugInfoParser = new DebugInfoParser(mth, debugOffset, insnArr);
debugInfoParser.process();
if (insnArr.length != 0) {
setMethodSourceLine(mth, insnArr);
}
if (!mth.getReturnType().equals(ArgType.VOID)) {
setLineForReturn(mth, insnArr);
}
}
/**
* Fix debug info for splitter 'return' instructions
*/
private void setLineForReturn(MethodNode mth, InsnNode[] insnArr) {
for (BlockNode exit : mth.getExitBlocks()) {
InsnNode ret = BlockUtils.getLastInsn(exit);
if (ret != null) {
InsnNode oldRet = insnArr[ret.getOffset()];
if (oldRet != ret) {
RegisterArg oldArg = (RegisterArg) oldRet.getArg(0);
RegisterArg newArg = (RegisterArg) ret.getArg(0);
newArg.mergeDebugInfo(oldArg.getType(), oldArg.getName());
@@ -48,6 +61,20 @@ public class DebugInfoVisitor extends AbstractVisitor {
}
}
}
mth.unloadInsnArr();
}
/**
* Set method source line from first instruction
*/
private void setMethodSourceLine(MethodNode mth, InsnNode[] insnArr) {
for (InsnNode insn : insnArr) {
if (insn != null) {
int line = insn.getSourceLine();
if (line != 0) {
mth.setSourceLine(line - 1);
}
return;
}
}
}
}
@@ -17,7 +17,7 @@ public class DepthTraversal {
visit(visitor, mth);
}
}
} catch (Throwable e) {
} catch (Exception e) {
ErrorsCounter.classError(cls,
e.getClass().getSimpleName() + " in pass: " + visitor.getClass().getSimpleName(), e);
}
@@ -29,9 +29,12 @@ public class DepthTraversal {
}
try {
visitor.visit(mth);
} catch (Throwable e) {
} catch (Exception e) {
ErrorsCounter.methodError(mth,
e.getClass().getSimpleName() + " in pass: " + visitor.getClass().getSimpleName(), e);
}
}
private DepthTraversal() {
}
}
@@ -23,42 +23,33 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class DotGraphVisitor extends AbstractVisitor {
private static final Logger LOG = LoggerFactory.getLogger(DotGraphVisitor.class);
private static final String NL = "\\l";
private static final boolean PRINT_DOMINATORS = false;
private final File dir;
private final boolean useRegions;
private final boolean rawInsn;
public static DotGraphVisitor dump(File outDir) {
return new DotGraphVisitor(outDir, false, false);
public static DotGraphVisitor dump() {
return new DotGraphVisitor(false, false);
}
public static DotGraphVisitor dumpRaw(File outDir) {
return new DotGraphVisitor(outDir, false, true);
public static DotGraphVisitor dumpRaw() {
return new DotGraphVisitor(false, true);
}
public static DotGraphVisitor dumpRegions(File outDir) {
return new DotGraphVisitor(outDir, true, false);
public static DotGraphVisitor dumpRegions() {
return new DotGraphVisitor(true, false);
}
public static DotGraphVisitor dumpRawRegions(File outDir) {
return new DotGraphVisitor(outDir, true, true);
public static DotGraphVisitor dumpRawRegions() {
return new DotGraphVisitor(true, true);
}
private DotGraphVisitor(File outDir, boolean useRegions, boolean rawInsn) {
this.dir = outDir;
private DotGraphVisitor(boolean useRegions, boolean rawInsn) {
this.useRegions = useRegions;
this.rawInsn = rawInsn;
LOG.debug("DOT {}{}graph dump dir: {}",
useRegions ? "regions " : "", rawInsn ? "raw " : "", outDir.getAbsolutePath());
}
@Override
@@ -66,12 +57,25 @@ public class DotGraphVisitor extends AbstractVisitor {
if (mth.isNoCode()) {
return;
}
new DumpDotGraph().process(mth);
File outRootDir = mth.root().getArgs().getOutDir();
new DumpDotGraph(outRootDir).process(mth);
}
public void save(File dir, MethodNode mth) {
if (mth.isNoCode()) {
return;
}
new DumpDotGraph(dir).process(mth);
}
private class DumpDotGraph {
private final CodeWriter dot = new CodeWriter();
private final CodeWriter conn = new CodeWriter();
private final File dir;
public DumpDotGraph(File dir) {
this.dir = dir;
}
public void process(MethodNode mth) {
dot.startLine("digraph \"CFG for");
@@ -96,7 +100,7 @@ public class DotGraphVisitor extends AbstractVisitor {
+ "(" + Utils.listToString(mth.getArguments(true)) + ") "));
String attrs = attributesString(mth);
if (attrs.length() != 0) {
if (!attrs.isEmpty()) {
dot.add(" | ").add(attrs);
}
dot.add("}\"];");
@@ -122,7 +126,7 @@ public class DotGraphVisitor extends AbstractVisitor {
processRegion(mth, h.getHandlerRegion());
}
}
Set<IBlock> regionsBlocks = new HashSet<IBlock>(mth.getBasicBlocks().size());
Set<IBlock> regionsBlocks = new HashSet<>(mth.getBasicBlocks().size());
RegionUtils.getAllRegionBlocks(mth.getRegion(), regionsBlocks);
for (ExceptionHandler handler : mth.getExceptionHandlers()) {
IContainer handlerRegion = handler.getHandlerRegion();
@@ -143,7 +147,7 @@ public class DotGraphVisitor extends AbstractVisitor {
dot.startLine("subgraph " + makeName(region) + " {");
dot.startLine("label = \"").add(r.toString());
String attrs = attributesString(r);
if (attrs.length() != 0) {
if (!attrs.isEmpty()) {
dot.add(" | ").add(attrs);
}
dot.add("\";");
@@ -171,11 +175,11 @@ public class DotGraphVisitor extends AbstractVisitor {
dot.add("label=\"{");
dot.add(String.valueOf(block.getId())).add("\\:\\ ");
dot.add(InsnUtils.formatOffset(block.getStartOffset()));
if (attrs.length() != 0) {
if (!attrs.isEmpty()) {
dot.add('|').add(attrs);
}
String insns = insertInsns(mth, block);
if (insns.length() != 0) {
if (!insns.isEmpty()) {
dot.add('|').add(insns);
}
dot.add("}\"];");
@@ -208,11 +212,11 @@ public class DotGraphVisitor extends AbstractVisitor {
dot.add("color=red,");
}
dot.add("label=\"{");
if (attrs.length() != 0) {
if (!attrs.isEmpty()) {
dot.add(attrs);
}
String insns = insertInsns(mth, block);
if (insns.length() != 0) {
if (!insns.isEmpty()) {
dot.add('|').add(insns);
}
dot.add("}\"];");
@@ -64,7 +64,7 @@ public class EnumVisitor extends AbstractVisitor {
String valuesMethod = "values()" + TypeGen.signature(ArgType.array(clsType));
// collect enum fields, remove synthetic
List<FieldNode> enumFields = new ArrayList<FieldNode>();
List<FieldNode> enumFields = new ArrayList<>();
for (FieldNode f : cls.getFields()) {
if (f.getAccessFlags().isEnum()) {
enumFields.add(f);
@@ -101,7 +101,7 @@ public class EnumVisitor extends AbstractVisitor {
// move enum specific instruction from static method to separate list
BlockNode staticBlock = staticMethod.getBasicBlocks().get(0);
List<InsnNode> enumPutInsns = new ArrayList<InsnNode>();
List<InsnNode> enumPutInsns = new ArrayList<>();
List<InsnNode> list = staticBlock.getInstructions();
int size = list.size();
for (int i = 0; i < size; i++) {
@@ -102,7 +102,7 @@ public class ExtractFieldInit extends AbstractVisitor {
private static class InitInfo {
private final MethodNode constrMth;
private final List<InsnNode> putInsns = new ArrayList<InsnNode>();
private final List<InsnNode> putInsns = new ArrayList<>();
private InitInfo(MethodNode constrMth) {
this.constrMth = constrMth;
@@ -122,7 +122,7 @@ public class ExtractFieldInit extends AbstractVisitor {
if (constrList.isEmpty()) {
return;
}
List<InitInfo> infoList = new ArrayList<InitInfo>(constrList.size());
List<InitInfo> infoList = new ArrayList<>(constrList.size());
for (MethodNode constrMth : constrList) {
if (constrMth.isNoCode() || constrMth.getBasicBlocks().isEmpty()) {
return;
@@ -151,7 +151,7 @@ public class ExtractFieldInit extends AbstractVisitor {
if (common == null) {
return;
}
Set<FieldInfo> fields = new HashSet<FieldInfo>();
Set<FieldInfo> fields = new HashSet<>();
for (InsnNode insn : common.getPutInsns()) {
FieldInfo fieldInfo = (FieldInfo) ((IndexInsnNode) insn).getIndex();
FieldNode field = cls.dex().resolveField(fieldInfo);
@@ -200,7 +200,7 @@ public class ExtractFieldInit extends AbstractVisitor {
} else {
return arg.isLiteral() || arg.isThis();
}
Set<RegisterArg> regs = new HashSet<RegisterArg>();
Set<RegisterArg> regs = new HashSet<>();
insn.getRegisterArgs(regs);
if (!regs.isEmpty()) {
for (RegisterArg reg : regs) {
@@ -213,7 +213,7 @@ public class ExtractFieldInit extends AbstractVisitor {
}
private static List<MethodNode> getConstructorsList(ClassNode cls) {
List<MethodNode> list = new ArrayList<MethodNode>();
List<MethodNode> list = new ArrayList<>();
for (MethodNode mth : cls.getMethods()) {
AccessInfo accFlags = mth.getAccessFlags();
if (!accFlags.isStatic() && accFlags.isConstructor()) {
@@ -230,7 +230,7 @@ public class ExtractFieldInit extends AbstractVisitor {
if (mth.isNoCode()) {
return Collections.emptyList();
}
List<InsnNode> assignInsns = new ArrayList<InsnNode>();
List<InsnNode> assignInsns = new ArrayList<>();
for (BlockNode block : mth.getBasicBlocks()) {
for (InsnNode insn : block.getInstructions()) {
if (insn.getType() == putInsn) {

Some files were not shown because too many files have changed in this diff Show More