Compare commits

..

135 Commits

Author SHA1 Message Date
Skylot fcb120a3ed core: suppress type error exception 2014-11-09 15:34:19 +03:00
Skylot 988628a2e7 core: fix variable declaration used in several loops 2014-11-09 14:55:33 +03:00
Skylot c24cdf5cc1 core: fix constructor instruction replacement 2014-11-08 20:38:22 +03:00
Skylot d748e004d2 core: fix missing parenthesis in conditions 2014-11-08 20:38:22 +03:00
Skylot 380b73d1b9 core: sort methods by source line number 2014-11-08 20:38:17 +03:00
Skylot ef85e29a9b core: improve 'break' and 'continue' insertion 2014-11-07 23:03:32 +03:00
Skylot 1daf5d1090 core: fix condition processing (resolve #25) 2014-11-07 21:39:27 +03:00
Skylot 9d2c0e4aea core: fix type inference and const inline for arrays 2014-11-07 20:07:18 +03:00
Skylot 7277ebb9c4 core: expand arrays for vararg arguments 2014-11-04 15:42:48 +03:00
Skylot c18074f6aa core: insert 'continue' instruction 2014-11-03 22:31:21 +03:00
Skylot 8a706193e7 core: fix indexed loop checks 2014-11-03 22:04:42 +03:00
Skylot 9d77f5f5df update dx library and dependencies 2014-11-03 15:22:49 +03:00
Skylot 6951d0e646 core: use NotNull and Nullable annotations 2014-11-03 15:13:52 +03:00
Skylot 73dd55eac2 core: fix code style issues 2014-11-03 15:11:48 +03:00
Skylot b5a9389cc6 core: fix variables inline in 'catch' block 2014-11-03 14:53:27 +03:00
Skylot d905c96fbe core: refactor 'catch' clause variable processing 2014-11-02 19:06:41 +03:00
Skylot 03f03f85af core: replace removed synthetic constructor 2014-11-01 15:46:57 +03:00
Skylot 2b00a8a406 core: disable parenthesis remove (break code in most cases) 2014-10-25 22:36:21 +04:00
Skylot f31c2dcd21 core: fix processing 'if' at loop end 2014-10-24 23:12:42 +04:00
Skylot 7699cfac02 tests: fix build on Windows 2014-10-23 21:30:46 +04:00
Skylot 5c48a457b4 fix code style issues 2014-10-19 19:07:15 +04:00
Skylot b5f439e1aa tests: reformat code 2014-10-19 18:01:32 +04:00
Skylot 202fe5a0a9 core: fix links for fields in nested classes 2014-10-18 23:08:15 +04:00
Skylot 68ccf57bd4 core: fix type detection for method arguments 2014-10-18 23:08:10 +04:00
Skylot 84970759d8 core: fix switch over enum with several enums in class 2014-10-14 22:38:29 +04:00
Skylot 53cac58ebe core: fix processing of last instruction in 'try' block 2014-10-11 22:44:26 +04:00
Skylot adc32ed319 fix minor issues 2014-10-11 22:44:26 +04:00
Skylot 7f0815a7b2 core tests: add option for compile test without debug info 2014-10-11 22:21:40 +04:00
Skylot 68f5565b63 core: improve processing of 'try/catch' and 'break' in loops 2014-10-11 22:21:30 +04:00
Skylot c552fb857d core tests: replace several classes in dynamic class loader, add additional checks 2014-10-07 22:19:54 +04:00
Skylot 8a4ec47b92 core: support break with label for simple cases 2014-09-29 23:44:36 +04:00
Skylot d281126337 core: fix processing try/catch in loop 2014-09-27 20:09:25 +04:00
Skylot 4fb6ada5ec core: fix type inference for phi nodes 2014-09-26 22:19:23 +04:00
Skylot ab924faa1e core: don't remove empty catch blocks 2014-09-22 22:48:25 +04:00
Skylot b12b129af7 travis: add jdk8 to build matrix 2014-09-22 22:34:44 +04:00
Skylot 017c6b4d42 core tests: compile decompiled code 2014-09-22 22:25:42 +04:00
Skylot d55cd5fbb4 core tests: organize directories 2014-09-22 22:02:42 +04:00
Skylot 13a6b1c8c6 core: add 'show inconsistent code' parameter 2014-09-20 13:43:55 +04:00
Skylot 0bc37e5d32 gui: fix jump manager 2014-09-20 13:01:20 +04:00
Skylot 46c8572887 core: restore switch over enum 2014-09-18 20:59:39 +04:00
Skylot e6b919007c gui: add new version notification 2014-09-16 22:03:18 +04:00
Skylot ac5a6096bb core: fix constructor call for moved arg (fix #20) 2014-09-13 18:55:17 +04:00
Skylot db527fbbda core: add api for write tests using smali 2014-09-13 18:55:17 +04:00
skylot 8f201f1fee Merge pull request #19 from NeoSpb/fix3
core: fix processing of debug info (markup of local variables)
2014-09-13 14:19:19 +04:00
Anton Dyachenko d1e0762c12 core: fix processing of debug info (markup of local variables) 2014-09-12 19:24:44 +04:00
Skylot 010ae99c69 core: restore simple for-each loop over iterable object 2014-09-07 16:49:02 +04:00
Skylot a4632d6e86 core: fix high memory usage while process conditions 2014-09-04 22:35:47 +04:00
Skylot 2a3162f869 core: don't set 'skip' flag for failed nested 'if' merge (issue #18) 2014-09-02 22:46:12 +04:00
skylot 2063fd0742 Merge pull request #17 from NeoSpb/fix2
Fix2 by NeoSpb
2014-09-02 20:47:14 +04:00
Anton Dyachenko 128fe8a839 core: fix resolving the instance field in the 2nd and more nested inner class 2014-09-02 20:05:15 +04:00
Anton Dyachenko 2478fc3a1b core: fix instance initializer producing (don't generate super() call) 2014-09-02 19:55:26 +04:00
Skylot 5a68d3bef7 core: restore for-each loop over array 2014-09-01 23:09:04 +04:00
Skylot 195eeceb62 core: restore simple indexed loops 2014-08-30 23:15:51 +04:00
Skylot ec8309af49 core: fix processing 'if' at loop end 2014-08-20 22:02:00 +04:00
skylot 627a4dc802 add contribution section to readme 2014-08-19 23:25:25 +04:00
Skylot e2018535ef core: add ternary conditions processing 2014-08-19 22:27:51 +04:00
Skylot ee56610f06 core: allow method name be same as class name (issue #15) 2014-08-18 20:45:50 +04:00
skylot fb9ff7748a Merge pull request #14 from NeoSpb/gui_preferences
gui: add saving preferences (open/save paths, flatten packages)
2014-08-17 13:22:08 +04:00
Anton Dyachenko cdfb46d9d3 gui: add saving preferences (open/save paths, flatten packages) 2014-08-17 12:01:46 +04:00
Skylot 5545a94a9e core: process nested ternary operators 2014-08-16 17:39:30 +04:00
Skylot 9e811d959b core: add method for print line numbers 2014-08-16 17:16:56 +04:00
Skylot 957d5394d2 refactor: add static methods for create DotGraphVisitor 2014-08-16 17:06:50 +04:00
Skylot 95afe1219e core: don't cache dex strings (old workaround for bug in dx) 2014-08-16 15:07:06 +04:00
Skylot 07937f1d71 bump version to 0.5.3 2014-08-16 15:06:55 +04:00
Skylot 671be0af0a add jadx-gui screenshot 2014-08-15 23:08:18 +04:00
Skylot 7e9278f992 don't hardcode maximum Java heap size 2014-08-15 22:39:34 +04:00
Skylot 9194441c47 add ASM to NOTICE file 2014-08-15 22:38:50 +04:00
Skylot 4f307c0085 core: allow subblock replace for 'if' region 2014-08-14 22:38:29 +04:00
Skylot 3bdda55102 core: inline filled array creation 2014-08-14 22:23:13 +04:00
Skylot b657b0fb1f core: fix 'if' processing in 'do/while' loop 2014-08-12 23:00:29 +04:00
Skylot 4935ae6da5 core: hide value parser constants 2014-08-12 22:58:20 +04:00
Skylot 72a50eae43 core: fix missing blocks in loop region 2014-08-11 22:29:10 +04:00
Skylot fa37b90cff core: fix processing try/catch in other catch 2014-08-10 22:36:42 +04:00
Skylot 052a8db606 core: resolve minor issues 2014-08-09 19:32:13 +04:00
Skylot 88ccba166e core: don't inline variables defined in 'try' and used in 'catch' 2014-08-08 22:10:10 +04:00
Skylot 58998089a6 core: redone 'if' structure checking 2014-08-07 22:20:47 +04:00
Skylot f0a73b329e core: fix processing conditions in loop 2014-08-06 22:28:29 +04:00
Skylot c97678a477 refactor: make ErrorsCounter non static 2014-08-05 22:48:31 +04:00
Skylot 2ad739275f core: handle special values for numbers 2014-08-04 22:07:10 +04:00
Skylot caad78885d core: check for duplicated code generation 2014-08-02 16:39:14 +04:00
Skylot a234227b9f core: fix errors in try/catch processing (issue #13) 2014-08-02 16:33:52 +04:00
Skylot 16f736e773 core: fix missing 'catch' code 2014-07-30 23:05:39 +04:00
Skylot 1fe24ad11d travis: cache dependencies 2014-07-29 23:41:10 +04:00
Skylot 33c5e0827a core: always check arguments before inline 2014-07-29 22:59:53 +04:00
Skylot cbd36aeb8f core: fix unused variables declaration 2014-07-29 22:34:18 +04:00
Skylot 2963bb3f41 core: fix issues reported by coverity 2014-07-28 23:19:48 +04:00
Skylot 09a6ceac63 gui: replace underline to color highlight (experimental) 2014-07-28 22:50:55 +04:00
Skylot 75d8a01cab core: improve error reporting 2014-07-28 22:50:55 +04:00
Skylot 0968f75e9a core: fix condition in loops (issue #9) 2014-07-28 22:50:42 +04:00
Skylot bc0db88afa update gradle and dependencies versions 2014-07-18 23:29:36 +04:00
Skylot 5f11f12d0c core: remove redundant spaces for enums 2014-07-18 21:21:24 +04:00
Skylot 2d18950542 core: add some integration tests 2014-07-17 23:32:18 +04:00
Skylot 50d314445a core: fix code style 2014-07-17 23:31:07 +04:00
Skylot f8d57d9265 core: decompile '.class' files 2014-07-15 23:45:25 +04:00
Skylot ebbe6db378 core: fix complex 'if' processing (issues #9 and #12) 2014-07-12 21:26:14 +04:00
skylot 543cad3a23 Merge pull request #11 from Fruiter/master
core: fix nested try-catch blocks processing
2014-07-07 21:13:23 +04:00
fruiter 41cc83dbf6 core: fix nested try-catch blocks processing 2014-07-06 20:15:20 -04:00
Skylot ce7101be88 core: always inline 'this' (issue #10) 2014-06-28 15:39:35 +04:00
Skylot 0a241e3a9c core tests: add custom string matchers 2014-06-28 15:38:50 +04:00
Skylot 37857e88ea core: fix switch statement processing (issue #9 case 2) 2014-06-24 14:08:20 +04:00
Skylot 6fbcf46a8b core: refactor return remover visitor 2014-06-24 14:08:20 +04:00
Skylot a36bc8f29a core: add serial uid to JadxRuntimeException 2014-06-24 14:08:20 +04:00
Skylot 813b7bca6e core: sort error nodes in execution report 2014-06-23 23:37:39 +04:00
Skylot e2945f2a42 core: limit region traversal iterations count 2014-06-23 23:37:39 +04:00
Skylot eaf623a560 gui: fix sync with editor 2014-06-20 20:20:35 +04:00
Skylot 26aa504590 core: guard endless regions processing 2014-06-20 17:08:07 +04:00
Skylot e4dde3f4b6 core: fix class cast exception 2014-06-15 21:35:34 +04:00
Skylot 9c90699c40 core: fix parsing of generic signature with inner classes 2014-06-15 17:42:48 +04:00
Skylot b67cd50e8a gui: add definitions search window 2014-06-14 19:38:24 +04:00
Skylot d2acaa03f5 core: guess variable name from assign instruction 2014-06-06 21:22:20 +04:00
Skylot f2aa4cd10b core: make better variables naming 2014-06-05 19:40:57 +04:00
Skylot b940b99e75 core: fix issues in variable names and try/catch blocks 2014-06-03 23:08:40 +04:00
Skylot 868e0706ea core: fix source line number parsing and saving 2014-05-31 16:58:03 +04:00
Skylot 324f544ba2 gui: show source line numbers 2014-05-31 16:17:40 +04:00
Skylot 0a1981f90e gui: add hyperlinks for classes and fields 2014-05-25 23:14:29 +04:00
Skylot 0a36bfb088 core: fix try-catch blocks processing 2014-05-24 19:55:29 +04:00
Skylot 0d94af099b core: improve 'if' detection with 'return' instruction 2014-05-18 00:28:43 +04:00
Skylot 4a6115ed64 core: refactor attribute storage 2014-05-14 21:12:39 +04:00
Skylot 42eb319751 fix issues reported by Coverity 2014-05-12 22:08:33 +04:00
Skylot 343bddc6ad core: fix 'break' detection in loop 2014-05-12 22:08:33 +04:00
Skylot 632a742ea9 core: fix method inline 2014-05-12 22:08:33 +04:00
Skylot 08c9d1228a core: inline 'cmp' instruction 2014-05-12 22:08:33 +04:00
Skylot 11d8b28fb4 core: fix variable rename 2014-05-12 22:08:33 +04:00
Skylot 12b6371209 core: fix basic block initial id 2014-05-12 22:08:33 +04:00
Skylot 24d22aaafb core: fix 'if' detection 2014-05-12 22:08:33 +04:00
Skylot ebf7822628 use spock framework for unit tests 2014-05-12 22:08:33 +04:00
Skylot 7669fa1582 update dependencies, add coveralls badge 2014-05-12 22:08:33 +04:00
Skylot e49ba61917 core: use SSA representation for instruction arguments 2014-05-12 22:08:32 +04:00
Skylot 96db1c2479 core: reformat code 2014-05-12 21:59:37 +04:00
skylot 7abdb41a9e Merge pull request #7 from bkerler/master
Add support for dx 1.8 library
2014-05-12 21:28:55 +04:00
Bjoern Kerler 14f6d2f3b0 Add support for dx 1.8 library 2014-05-12 09:12:34 +02:00
Skylot 4e4b4975ad core: fix method redecompilation (issue #6) 2014-04-24 23:44:30 +04:00
skylot 93fafcf886 Merge pull request #5 from dnet/master
removed unused private method getCodePanel(int)
2014-04-02 22:03:57 +04:00
András Veres-Szentkirályi 82cc88a1b9 removed unused private method getCodePanel(int) 2014-04-02 17:10:34 +02:00
Skylot 5c94e0bccc core: improve exceptions handling 2014-03-27 23:24:20 +04:00
401 changed files with 15972 additions and 4439 deletions
+11 -1
View File
@@ -1,12 +1,22 @@
language: java
jdk:
- oraclejdk8
- oraclejdk7
- openjdk7
- openjdk6
before_install:
- chmod +x gradlew
script:
- TERM=dumb ./gradlew clean build dist
after_success:
- TERM=dumb ./gradlew jacocoTestReport coveralls
cache:
directories:
- $HOME/.gradle
notifications:
email:
- skylot@gmail.com
+35
View File
@@ -105,6 +105,41 @@ as published by the Free Software Foundation.
*******************************************************************************
ASM library:
*******************************************************************************
Copyright (c) 2000-2011 INRIA, France Telecom
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holders nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
THE POSSIBILITY OF SUCH DAMAGE.
*******************************************************************************
Jadx-gui components
===================
+37 -10
View File
@@ -1,24 +1,27 @@
## JADX
## 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)
**jadx** - Dex to Java decompiler
Command line and GUI tools for produce Java source code from Android Dex and Apk files
Note: jadx-gui now in experimental stage
![jadx-gui screenshot](http://skylot.github.io/jadx/jadx-gui.png)
### Downloads
- [unstable](https://drone.io/github.com/skylot/jadx/files)
[![Build Status](https://drone.io/github.com/skylot/jadx/status.png)](https://drone.io/github.com/skylot/jadx/latest)
[![Build Status](https://travis-ci.org/skylot/jadx.png?branch=master)](https://travis-ci.org/skylot/jadx)
- from [github](https://github.com/skylot/jadx/releases)
- from [sourceforge](http://sourceforge.net/projects/jadx/files/)
- from [sourceforge](http://sourceforge.net/projects/jadx/files/)
### Building from source
### Building from source
git clone https://github.com/skylot/jadx.git
cd jadx
./gradlew dist
(on Windows, use `gradlew.bat` instead of `./gradlew`)
Scripts for run jadx will be placed in `build/jadx/bin`
@@ -36,7 +39,7 @@ Run **jadx** on itself:
### Usage
```
jadx[-gui] [options] <input file> (.dex, .apk or .jar)
jadx[-gui] [options] <input file> (.dex, .apk, .jar or .class)
options:
-d, --output-dir - output directory
-j, --threads-count - processing threads count
@@ -49,6 +52,30 @@ Example:
jadx -d out classes.dex
```
### Troubleshooting
##### Out of memory error:
- Reduce processing threads count (`-j` option)
- Increase maximum java heap size:
* command line (example for linux):
`JAVA_OPTS="-Xmx4G" jadx -j 1 some.apk`
* 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 2013 by Skylot*
*Copyright 2014 by Skylot*
+34 -14
View File
@@ -5,20 +5,18 @@ apply plugin: 'sonar-runner'
subprojects {
apply plugin: 'java'
apply plugin: 'groovy'
apply plugin: 'jacoco'
apply plugin: 'idea'
apply plugin: 'eclipse'
sourceCompatibility = 1.6
targetCompatibility = 1.6
apply plugin: 'coveralls'
version = jadxVersion
gradle.projectsEvaluated {
tasks.withType(Compile) {
if (!"${it}".contains(':jadx-samples:')) {
options.compilerArgs << '-Xlint' << '-Xlint:unchecked' << '-Xlint:deprecation'
}
tasks.withType(JavaCompile) {
sourceCompatibility = JavaVersion.VERSION_1_6
targetCompatibility = JavaVersion.VERSION_1_6
if (!"$it".contains(':jadx-samples:')) {
options.compilerArgs << '-Xlint' << '-Xlint:unchecked' << '-Xlint:deprecation'
}
}
@@ -30,14 +28,36 @@ subprojects {
}
dependencies {
compile 'org.slf4j:slf4j-api:1.7.6'
compile 'org.slf4j:slf4j-api:1.7.7'
testCompile 'ch.qos.logback:logback-classic:1.1.2'
testCompile 'junit:junit:4.11'
testCompile 'org.mockito:mockito-core:1.9.5'
testCompile 'ch.qos.logback:logback-classic:1.1.1'
testCompile 'org.mockito:mockito-core:1.10.10'
testCompile 'org.spockframework:spock-core:0.7-groovy-2.0'
testCompile 'cglib:cglib-nodep:3.1'
}
repositories {
mavenCentral()
jcenter()
}
jacocoTestReport {
reports {
xml.enabled = true // coveralls plugin depends on xml format report
html.enabled = true
}
}
}
buildscript {
repositories {
mavenCentral()
}
dependencies {
// setup coveralls (http://coveralls.io/) see http://github.com/kt3k/coveralls-gradle-plugin
classpath 'org.kt3k.gradle.plugin:coveralls-gradle-plugin:0.6.1'
}
}
@@ -69,5 +89,5 @@ task clean(type: Delete) {
}
task wrapper(type: Wrapper) {
gradleVersion = '1.11'
gradleVersion = '2.0'
}
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=http\://services.gradle.org/distributions/gradle-1.11-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-2.0-bin.zip
+2 -12
View File
@@ -5,18 +5,8 @@ applicationName = 'jadx'
dependencies {
compile(project(':jadx-core'))
compile 'com.beust:jcommander:1.30'
compile 'ch.qos.logback:logback-classic:1.1.1'
}
startScripts {
doLast {
// increase default max heap size
String var = 'DEFAULT_JVM_OPTS='
String args = '-Xmx1300M'
unixScript.text = unixScript.text.replace(var + '""', var + '"' + args + '"')
windowsScript.text = windowsScript.text.replace(var, var + args)
}
compile 'com.beust:jcommander:1.35'
compile 'ch.qos.logback:logback-classic:1.1.2'
}
applicationDistribution.with {
+23 -23
View File
@@ -1,7 +1,6 @@
package jadx.cli;
import jadx.api.Decompiler;
import jadx.core.utils.ErrorsCounter;
import jadx.api.JadxDecompiler;
import jadx.core.utils.exceptions.JadxException;
import java.io.File;
@@ -12,39 +11,39 @@ import org.slf4j.LoggerFactory;
public class JadxCLI {
private static final Logger LOG = LoggerFactory.getLogger(JadxCLI.class);
public static void main(String[] args) {
public static void main(String[] args) throws JadxException {
try {
JadxCLIArgs jadxArgs = new JadxCLIArgs(args);
checkArgs(jadxArgs);
processAndSave(jadxArgs);
} catch (Exception e) {
LOG.error(e.getMessage());
JadxCLIArgs jadxArgs = new JadxCLIArgs();
if (processArgs(jadxArgs, args)) {
processAndSave(jadxArgs);
}
} catch (Throwable e) {
LOG.error("jadx error: " + e.getMessage(), e);
System.exit(1);
}
}
private static void processAndSave(JadxCLIArgs jadxArgs) {
try {
Decompiler jadx = new Decompiler(jadxArgs);
jadx.loadFiles(jadxArgs.getInput());
jadx.setOutputDir(jadxArgs.getOutDir());
jadx.save();
static void processAndSave(JadxCLIArgs jadxArgs) throws JadxException {
JadxDecompiler jadx = new JadxDecompiler(jadxArgs);
jadx.setOutputDir(jadxArgs.getOutDir());
jadx.loadFiles(jadxArgs.getInput());
jadx.save();
if (jadx.getErrorsCount() != 0) {
jadx.printErrorsReport();
LOG.error("finished with errors");
} else {
LOG.info("done");
} catch (Throwable e) {
LOG.error("jadx error:", e);
}
int errorsCount = ErrorsCounter.getErrorCount();
if (errorsCount != 0) {
ErrorsCounter.printReport();
}
System.exit(errorsCount);
}
private static void checkArgs(JadxCLIArgs jadxArgs) throws JadxException {
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();
System.exit(1);
return false;
}
File outputDir = jadxArgs.getOutDir();
if (outputDir == null) {
@@ -64,5 +63,6 @@ public class JadxCLI {
if (outputDir.exists() && !outputDir.isDirectory()) {
throw new JadxException("Output directory exists as file " + outputDir);
}
return true;
}
}
@@ -1,7 +1,7 @@
package jadx.cli;
import jadx.api.IJadxArgs;
import jadx.core.Consts;
import jadx.api.JadxDecompiler;
import jadx.core.utils.exceptions.JadxException;
import java.io.File;
@@ -20,7 +20,7 @@ import com.beust.jcommander.ParameterException;
public final class JadxCLIArgs implements IJadxArgs {
@Parameter(description = "<input file> (.dex, .apk or .jar)")
@Parameter(description = "<input file> (.dex, .apk, .jar or .class)")
protected List<String> files;
@Parameter(names = {"-d", "--output-dir"}, description = "output directory")
@@ -29,9 +29,12 @@ public final class JadxCLIArgs implements IJadxArgs {
@Parameter(names = {"-j", "--threads-count"}, description = "processing threads count")
protected int threadsCount = Runtime.getRuntime().availableProcessors();
@Parameter(names = {"-f", "--fallback"}, description = "make simple dump (using goto instead of 'if', 'for', etc)", help = true)
@Parameter(names = {"-f", "--fallback"}, description = "make simple dump (using goto instead of 'if', 'for', etc)")
protected boolean fallbackMode = false;
@Parameter(names = {"--show-bad-code"}, description = "show inconsistent code (incorrectly decompiled)")
protected boolean showInconsistentCode = false;
@Parameter(names = {"--cfg"}, description = "save methods control flow graph to dot file")
protected boolean cfgOutput = false;
@@ -47,25 +50,25 @@ public final class JadxCLIArgs implements IJadxArgs {
private final List<File> input = new ArrayList<File>(1);
private File outputDir;
public JadxCLIArgs(String[] args) {
parse(args);
processArgs();
public boolean processArgs(String[] args) {
return parse(args) && process();
}
private void parse(String[] args) {
private boolean parse(String[] args) {
try {
new JCommander(this, args);
return true;
} catch (ParameterException e) {
System.err.println("Arguments parse error: " + e.getMessage());
printUsage();
System.exit(1);
return false;
}
}
public void processArgs() {
private boolean process() {
if (isPrintHelp()) {
printUsage();
System.exit(0);
return false;
}
try {
if (threadsCount <= 0) {
@@ -95,8 +98,9 @@ public final class JadxCLIArgs implements IJadxArgs {
} catch (JadxException e) {
System.err.println("ERROR: " + e.getMessage());
printUsage();
System.exit(1);
return false;
}
return true;
}
public void printUsage() {
@@ -104,7 +108,7 @@ public final class JadxCLIArgs implements IJadxArgs {
// print usage in not sorted fields order (by default its sorted by description)
PrintStream out = System.out;
out.println();
out.println("jadx - dex to java decompiler, version: " + Consts.JADX_VERSION);
out.println("jadx - dex to java decompiler, version: " + JadxDecompiler.getVersion());
out.println();
out.println("usage: jadx [options] " + jc.getMainParameterDescription());
out.println("options:");
@@ -147,6 +151,7 @@ public final class JadxCLIArgs implements IJadxArgs {
return input;
}
@Override
public File getOutDir() {
return outputDir;
}
@@ -179,6 +184,11 @@ public final class JadxCLIArgs implements IJadxArgs {
return fallbackMode;
}
@Override
public boolean isShowInconsistentCode() {
return showInconsistentCode;
}
@Override
public boolean isVerbose() {
return verbose;
+11 -2
View File
@@ -1,8 +1,17 @@
ext.jadxClasspath = 'clsp-data/android-4.3.jar'
dependencies {
compile 'com.google.android.tools:dx:1.7'
runtime files(jadxClasspath)
compile files('lib/dx-1.10.jar')
compile 'org.ow2.asm:asm:5.0.3'
compile 'com.intellij:annotations:12.0'
testCompile 'org.smali:smali:2.0.3'
}
task packTests(type: Jar) {
classifier = 'tests'
from sourceSets.test.output
}
Binary file not shown.
@@ -30,6 +30,10 @@ public final class CodePosition {
return offset;
}
public boolean isSet() {
return line != 0 || offset != 0;
}
@Override
public boolean equals(Object o) {
if (this == o) {
@@ -1,7 +1,14 @@
package jadx.api;
import java.io.File;
public class DefaultJadxArgs implements IJadxArgs {
@Override
public File getOutDir() {
return new File("jadx-output");
}
@Override
public int getThreadsCount() {
return Runtime.getRuntime().availableProcessors();
@@ -22,6 +29,11 @@ public class DefaultJadxArgs implements IJadxArgs {
return false;
}
@Override
public boolean isShowInconsistentCode() {
return false;
}
@Override
public boolean isVerbose() {
return false;
@@ -1,6 +1,10 @@
package jadx.api;
import java.io.File;
public interface IJadxArgs {
File getOutDir();
int getThreadsCount();
boolean isCFGOutput();
@@ -9,5 +13,7 @@ public interface IJadxArgs {
boolean isFallbackMode();
boolean isShowInconsistentCode();
boolean isVerbose();
}
@@ -7,8 +7,8 @@ import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.visitors.IDexTreeVisitor;
import jadx.core.dex.visitors.SaveCode;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.utils.files.InputFile;
@@ -22,7 +22,6 @@ import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
@@ -31,7 +30,7 @@ import org.slf4j.LoggerFactory;
/**
* Jadx API usage example:
* <pre><code>
* Decompiler jadx = new Decompiler();
* JadxDecompiler jadx = new JadxDecompiler();
* jadx.loadFile(new File("classes.dex"));
* jadx.setOutputDir(new File("out"));
* jadx.save();
@@ -44,8 +43,8 @@ import org.slf4j.LoggerFactory;
* }
* </code></pre>
*/
public final class Decompiler {
private static final Logger LOG = LoggerFactory.getLogger(Decompiler.class);
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>();
@@ -56,13 +55,14 @@ public final class Decompiler {
private List<IDexTreeVisitor> passes;
private List<JavaClass> classes;
public Decompiler() {
this.args = new DefaultJadxArgs();
init();
public JadxDecompiler() {
this(new DefaultJadxArgs());
}
public Decompiler(IJadxArgs jadxArgs) {
public JadxDecompiler(IJadxArgs jadxArgs) {
this.args = jadxArgs;
this.outDir = jadxArgs.getOutDir();
reset();
init();
}
@@ -73,22 +73,36 @@ public final class Decompiler {
void init() {
if (outDir == null) {
outDir = new File("jadx-output");
outDir = new DefaultJadxArgs().getOutDir();
}
this.passes = Jadx.getPassesList(args, outDir);
}
public void loadFile(File file) throws IOException, DecodeException {
void reset() {
ClassInfo.clearCache();
classes = null;
root = null;
}
public static String getVersion() {
return Jadx.getVersion();
}
public void loadFile(File file) throws JadxException {
loadFiles(Collections.singletonList(file));
}
public void loadFiles(List<File> files) throws IOException, DecodeException {
public void loadFiles(List<File> files) throws JadxException {
if (files.isEmpty()) {
throw new JadxRuntimeException("Empty file list");
throw new JadxException("Empty file list");
}
inputFiles.clear();
for (File file : files) {
inputFiles.add(new InputFile(file));
try {
inputFiles.add(new InputFile(file));
} catch (IOException e) {
throw new JadxException("Error load file: " + file, e);
}
}
parse();
}
@@ -99,28 +113,25 @@ public final class Decompiler {
ex.shutdown();
ex.awaitTermination(1, TimeUnit.DAYS);
} catch (InterruptedException e) {
LOG.error("Save interrupted", e);
throw new JadxRuntimeException("Save interrupted", e);
}
}
public ThreadPoolExecutor getSaveExecutor() {
public ExecutorService getSaveExecutor() {
if (root == null) {
throw new JadxRuntimeException("No loaded files");
}
int threadsCount = args.getThreadsCount();
LOG.debug("processing threads count: {}", threadsCount);
final List<IDexTreeVisitor> passList = new ArrayList<IDexTreeVisitor>(passes);
SaveCode savePass = new SaveCode(outDir, args);
passList.add(savePass);
LOG.info("processing ...");
ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(threadsCount);
for (final ClassNode cls : root.getClasses(false)) {
ExecutorService executor = Executors.newFixedThreadPool(threadsCount);
for (final JavaClass cls : getClasses()) {
executor.execute(new Runnable() {
@Override
public void run() {
ProcessClass.process(cls, passList);
cls.decompile();
SaveCode.save(outDir, args, cls.getClassNode());
}
});
}
@@ -128,11 +139,14 @@ public final class Decompiler {
}
public List<JavaClass> getClasses() {
if (root == null) {
return Collections.emptyList();
}
if (classes == null) {
List<ClassNode> classNodeList = root.getClasses(false);
List<JavaClass> clsList = new ArrayList<JavaClass>(classNodeList.size());
for (ClassNode classNode : classNodeList) {
clsList.add(new JavaClass(this, classNode));
clsList.add(new JavaClass(classNode, this));
}
classes = Collections.unmodifiableList(clsList);
}
@@ -140,8 +154,12 @@ public final class Decompiler {
}
public List<JavaPackage> getPackages() {
List<JavaClass> classList = getClasses();
if (classList.isEmpty()) {
return Collections.emptyList();
}
Map<String, List<JavaClass>> map = new HashMap<String, List<JavaClass>>();
for (JavaClass javaClass : getClasses()) {
for (JavaClass javaClass : classList) {
String pkg = javaClass.getPackage();
List<JavaClass> clsList = map.get(pkg);
if (clsList == null) {
@@ -159,7 +177,7 @@ public final class Decompiler {
Collections.sort(pkg.getClasses(), new Comparator<JavaClass>() {
@Override
public int compare(JavaClass o1, JavaClass o2) {
return o1.getShortName().compareTo(o2.getShortName());
return o1.getName().compareTo(o2.getName());
}
});
}
@@ -167,7 +185,17 @@ public final class Decompiler {
}
public int getErrorsCount() {
return ErrorsCounter.getErrorCount();
if (root == null) {
return 0;
}
return root.getErrorsCounter().getErrorCount();
}
public void printErrorsReport() {
if (root == null) {
return;
}
root.getErrorsCounter().printReport();
}
void parse() throws DecodeException {
@@ -177,14 +205,7 @@ public final class Decompiler {
root.load(inputFiles);
}
private void reset() {
ClassInfo.clearCache();
ErrorsCounter.reset();
classes = null;
}
void processClass(ClassNode cls) {
LOG.info("processing class {} ...", cls);
ProcessClass.process(cls, passes);
}
@@ -203,4 +224,9 @@ public final class Decompiler {
}
return null;
}
@Override
public String toString() {
return "jadx decompiler " + getVersion();
}
}
+69 -38
View File
@@ -1,13 +1,12 @@
package jadx.api;
import jadx.core.codegen.CodeWriter;
import jadx.core.dex.attributes.AttributeFlag;
import jadx.core.dex.attributes.LineAttrNode;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.nodes.LineAttrNode;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.exceptions.JadxRuntimeException;
import java.util.ArrayList;
import java.util.Collections;
@@ -15,18 +14,29 @@ import java.util.Comparator;
import java.util.List;
import java.util.Map;
public final class JavaClass {
public final class JavaClass implements JavaNode {
private final Decompiler decompiler;
private final JadxDecompiler decompiler;
private final ClassNode cls;
private final JavaClass parent;
private List<JavaClass> innerClasses = Collections.emptyList();
private List<JavaField> fields = Collections.emptyList();
private List<JavaMethod> methods = Collections.emptyList();
JavaClass(Decompiler decompiler, ClassNode classNode) {
JavaClass(ClassNode classNode, JadxDecompiler decompiler) {
this.decompiler = decompiler;
this.cls = classNode;
this.parent = null;
}
/**
* Inner classes constructor
*/
JavaClass(ClassNode classNode, JavaClass parent) {
this.decompiler = null;
this.cls = classNode;
this.parent = parent;
}
public String getCode() {
@@ -35,12 +45,15 @@ public final class JavaClass {
decompile();
code = cls.getCode();
}
return code != null ? code.toString() : "error processing class";
if (code == null) {
return "";
}
return code.toString();
}
public void decompile() {
if (decompiler == null) {
throw new JadxRuntimeException("Can't decompile inner class");
return;
}
if (cls.getCode() == null) {
decompiler.processClass(cls);
@@ -57,8 +70,8 @@ public final class JavaClass {
if (inClsCount != 0) {
List<JavaClass> list = new ArrayList<JavaClass>(inClsCount);
for (ClassNode inner : cls.getInnerClasses()) {
if (!inner.getAttributes().contains(AttributeFlag.DONT_GENERATE)) {
JavaClass javaClass = new JavaClass(null, inner);
if (!inner.contains(AFlag.DONT_GENERATE)) {
JavaClass javaClass = new JavaClass(inner, this);
javaClass.load();
list.add(javaClass);
}
@@ -70,8 +83,8 @@ public final class JavaClass {
if (fieldsCount != 0) {
List<JavaField> flds = new ArrayList<JavaField>(fieldsCount);
for (FieldNode f : cls.getFields()) {
if (!f.getAttributes().contains(AttributeFlag.DONT_GENERATE)) {
flds.add(new JavaField(f));
if (!f.contains(AFlag.DONT_GENERATE)) {
flds.add(new JavaField(f, this));
}
}
this.fields = Collections.unmodifiableList(flds);
@@ -81,7 +94,7 @@ public final class JavaClass {
if (methodsCount != 0) {
List<JavaMethod> mths = new ArrayList<JavaMethod>(methodsCount);
for (MethodNode m : cls.getMethods()) {
if (!m.getAttributes().contains(AttributeFlag.DONT_GENERATE)) {
if (!m.contains(AFlag.DONT_GENERATE)) {
mths.add(new JavaMethod(this, m));
}
}
@@ -103,55 +116,73 @@ public final class JavaClass {
public CodePosition getDefinitionPosition(int line, int offset) {
Map<CodePosition, Object> map = getCodeAnnotations();
Object obj = map.get(new CodePosition(line, offset));
if (obj instanceof LineAttrNode) {
ClassNode clsNode = null;
if (obj instanceof ClassNode) {
clsNode = (ClassNode) obj;
} else if (obj instanceof MethodNode) {
clsNode = ((MethodNode) obj).getParentClass();
} else if (obj instanceof FieldNode) {
clsNode = ((FieldNode) obj).getParentClass();
}
if (clsNode == null) {
return null;
}
clsNode = clsNode.getParentClass();
JavaClass jCls = decompiler.findJavaClass(clsNode);
if (jCls == null) {
return null;
}
jCls.decompile();
int defLine = ((LineAttrNode) obj).getDecompiledLine();
return new CodePosition(jCls, defLine, 0);
if (!(obj instanceof LineAttrNode)) {
return null;
}
return null;
ClassNode clsNode = null;
if (obj instanceof ClassNode) {
clsNode = (ClassNode) obj;
} else if (obj instanceof MethodNode) {
clsNode = ((MethodNode) obj).getParentClass();
} else if (obj instanceof FieldNode) {
clsNode = ((FieldNode) obj).getParentClass();
}
if (clsNode == null) {
return null;
}
clsNode = clsNode.getTopParentClass();
JavaClass jCls = decompiler.findJavaClass(clsNode);
if (jCls == null) {
return null;
}
jCls.decompile();
int defLine = ((LineAttrNode) obj).getDecompiledLine();
if (defLine == 0) {
return null;
}
return new CodePosition(jCls, defLine, 0);
}
public Integer getSourceLine(int decompiledLine) {
decompile();
return cls.getCode().getLineMapping().get(decompiledLine);
}
@Override
public String getName() {
return cls.getShortName();
}
@Override
public String getFullName() {
return cls.getFullName();
}
public String getShortName() {
return cls.getShortName();
}
public String getPackage() {
return cls.getPackage();
}
@Override
public JavaClass getDeclaringClass() {
return parent;
}
public AccessInfo getAccessInfo() {
return cls.getAccessFlags();
}
public List<JavaClass> getInnerClasses() {
decompile();
return innerClasses;
}
public List<JavaField> getFields() {
decompile();
return fields;
}
public List<JavaMethod> getMethods() {
decompile();
return methods;
}
@@ -4,18 +4,31 @@ import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.FieldNode;
public final class JavaField {
public final class JavaField implements JavaNode {
private final FieldNode field;
private final JavaClass parent;
public JavaField(FieldNode f) {
JavaField(FieldNode f, JavaClass cls) {
this.field = f;
this.parent = cls;
}
@Override
public String getName() {
return field.getName();
}
@Override
public String getFullName() {
return parent.getFullName() + "." + field.getName();
}
@Override
public JavaClass getDeclaringClass() {
return parent;
}
public AccessInfo getAccessFlags() {
return field.getAccessFlags();
}
@@ -6,19 +6,26 @@ import jadx.core.dex.nodes.MethodNode;
import java.util.List;
public final class JavaMethod {
public final class JavaMethod implements JavaNode {
private final MethodNode mth;
private final JavaClass parent;
public JavaMethod(JavaClass cls, MethodNode m) {
JavaMethod(JavaClass cls, MethodNode m) {
this.parent = cls;
this.mth = m;
}
@Override
public String getName() {
return mth.getMethodInfo().getName();
return mth.getName();
}
@Override
public String getFullName() {
return mth.getMethodInfo().getFullName();
}
@Override
public JavaClass getDeclaringClass() {
return parent;
}
@@ -0,0 +1,10 @@
package jadx.api;
public interface JavaNode {
String getName();
String getFullName();
JavaClass getDeclaringClass();
}
@@ -2,7 +2,7 @@ package jadx.api;
import java.util.List;
public final class JavaPackage implements Comparable<JavaPackage> {
public final class JavaPackage implements JavaNode, Comparable<JavaPackage> {
private final String name;
private final List<JavaClass> classes;
@@ -11,14 +11,26 @@ public final class JavaPackage implements Comparable<JavaPackage> {
this.classes = classes;
}
@Override
public String getName() {
return name;
}
@Override
public String getFullName() {
// TODO: store full package name
return name;
}
public List<JavaClass> getClasses() {
return classes;
}
@Override
public JavaClass getDeclaringClass() {
return null;
}
@Override
public int compareTo(JavaPackage o) {
return name.compareTo(o.name);
@@ -1,8 +1,6 @@
package jadx.core;
public class Consts {
public static final String JADX_VERSION = Jadx.getVersion();
public static final boolean DEBUG = false;
public static final String CLASS_OBJECT = "java.lang.Object";
+31 -19
View File
@@ -6,6 +6,7 @@ import jadx.core.dex.visitors.BlockMakerVisitor;
import jadx.core.dex.visitors.ClassModifier;
import jadx.core.dex.visitors.CodeShrinker;
import jadx.core.dex.visitors.ConstInlinerVisitor;
import jadx.core.dex.visitors.DebugInfoVisitor;
import jadx.core.dex.visitors.DotGraphVisitor;
import jadx.core.dex.visitors.EnumVisitor;
import jadx.core.dex.visitors.FallbackModeVisitor;
@@ -13,17 +14,21 @@ import jadx.core.dex.visitors.IDexTreeVisitor;
import jadx.core.dex.visitors.MethodInlineVisitor;
import jadx.core.dex.visitors.ModVisitor;
import jadx.core.dex.visitors.PrepareForCodeGen;
import jadx.core.dex.visitors.ReSugarCode;
import jadx.core.dex.visitors.SimplifyVisitor;
import jadx.core.dex.visitors.regions.CheckRegions;
import jadx.core.dex.visitors.regions.IfRegionVisitor;
import jadx.core.dex.visitors.regions.LoopRegionVisitor;
import jadx.core.dex.visitors.regions.ProcessVariables;
import jadx.core.dex.visitors.regions.RegionMakerVisitor;
import jadx.core.dex.visitors.typeresolver.FinishTypeResolver;
import jadx.core.dex.visitors.typeresolver.TypeResolver;
import jadx.core.dex.visitors.regions.ReturnVisitor;
import jadx.core.dex.visitors.ssa.EliminatePhiNodes;
import jadx.core.dex.visitors.ssa.SSATransform;
import jadx.core.dex.visitors.typeinference.FinishTypeInference;
import jadx.core.dex.visitors.typeinference.TypeInference;
import jadx.core.utils.Utils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
@@ -51,39 +56,43 @@ public class Jadx {
passes.add(new FallbackModeVisitor());
} else {
passes.add(new BlockMakerVisitor());
passes.add(new TypeResolver());
passes.add(new SSATransform());
passes.add(new DebugInfoVisitor());
passes.add(new TypeInference());
if (args.isRawCFGOutput()) {
passes.add(new DotGraphVisitor(outDir, false, true));
passes.add(DotGraphVisitor.dumpRaw(outDir));
}
passes.add(new ConstInlinerVisitor());
passes.add(new FinishTypeResolver());
passes.add(new FinishTypeInference());
passes.add(new EliminatePhiNodes());
passes.add(new ModVisitor());
passes.add(new EnumVisitor());
passes.add(new CodeShrinker());
passes.add(new ReSugarCode());
if (args.isCFGOutput()) {
passes.add(new DotGraphVisitor(outDir, false));
passes.add(DotGraphVisitor.dump(outDir));
}
passes.add(new CodeShrinker());
passes.add(new RegionMakerVisitor());
passes.add(new IfRegionVisitor());
passes.add(new ReturnVisitor());
passes.add(new CodeShrinker());
passes.add(new SimplifyVisitor());
passes.add(new ProcessVariables());
passes.add(new CheckRegions());
if (args.isCFGOutput()) {
passes.add(new DotGraphVisitor(outDir, true));
passes.add(DotGraphVisitor.dumpRegions(outDir));
}
passes.add(new MethodInlineVisitor());
passes.add(new ClassModifier());
passes.add(new PrepareForCodeGen());
passes.add(new LoopRegionVisitor());
passes.add(new ProcessVariables());
}
passes.add(new CodeGen(args));
return passes;
@@ -91,15 +100,18 @@ public class Jadx {
public static String getVersion() {
try {
Enumeration<URL> resources = Utils.class.getClassLoader().getResources("META-INF/MANIFEST.MF");
while (resources.hasMoreElements()) {
Manifest manifest = new Manifest(resources.nextElement().openStream());
String ver = manifest.getMainAttributes().getValue("jadx-version");
if (ver != null) {
return ver;
ClassLoader classLoader = Utils.class.getClassLoader();
if (classLoader != null) {
Enumeration<URL> resources = classLoader.getResources("META-INF/MANIFEST.MF");
while (resources.hasMoreElements()) {
Manifest manifest = new Manifest(resources.nextElement().openStream());
String ver = manifest.getMainAttributes().getValue("jadx-version");
if (ver != null) {
return ver;
}
}
}
} catch (IOException e) {
} catch (Exception e) {
LOG.error("Can't get manifest file", e);
}
return "dev";
@@ -3,7 +3,6 @@ package jadx.core;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.visitors.DepthTraversal;
import jadx.core.dex.visitors.IDexTreeVisitor;
import jadx.core.utils.exceptions.DecodeException;
import java.util.List;
@@ -22,10 +21,8 @@ public final class ProcessClass {
for (IDexTreeVisitor visitor : passes) {
DepthTraversal.visit(visitor, cls);
}
} catch (DecodeException e) {
LOG.error("Decode exception: " + cls, e);
} catch (Exception e) {
LOG.error("Class process exception: " + cls, e);
LOG.error("Class process exception: {}", cls, e);
} finally {
cls.unload();
}
@@ -3,9 +3,9 @@ package jadx.core.clsp;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.utils.files.FileUtils;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
@@ -65,6 +65,9 @@ public class ClsSet {
for (ClassNode cls : list) {
if (cls.getAccessFlags().isPublic()) {
NClass nClass = getCls(cls.getRawName(), names);
if (nClass == null) {
throw new JadxRuntimeException("Missing class: " + cls);
}
nClass.setParents(makeParentsArray(cls, names));
classes[k] = nClass;
k++;
@@ -93,49 +96,56 @@ public class ClsSet {
private static NClass getCls(String fullName, Map<String, NClass> names) {
NClass id = names.get(fullName);
if (id == null && !names.containsKey(fullName)) {
LOG.warn("Class not found: " + fullName);
LOG.warn("Class not found: {}", fullName);
}
return id;
}
void save(File output) throws IOException {
Utils.makeDirsForFile(output);
FileUtils.makeDirsForFile(output);
BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(output));
String outputName = output.getName();
if (outputName.endsWith(CLST_EXTENSION)) {
save(outputStream);
} else if (outputName.endsWith(".jar")) {
ZipOutputStream out = new ZipOutputStream(outputStream);
try {
out.putNextEntry(new ZipEntry(CLST_PKG_PATH + "/" + CLST_FILENAME));
save(out);
out.closeEntry();
} finally {
out.close();
outputStream.close();
try {
String outputName = output.getName();
if (outputName.endsWith(CLST_EXTENSION)) {
save(outputStream);
} else if (outputName.endsWith(".jar")) {
ZipOutputStream out = new ZipOutputStream(outputStream);
try {
out.putNextEntry(new ZipEntry(CLST_PKG_PATH + "/" + CLST_FILENAME));
save(out);
out.closeEntry();
} finally {
out.close();
}
} else {
throw new JadxRuntimeException("Unknown file format: " + outputName);
}
} else {
throw new JadxRuntimeException("Unknown file format: " + outputName);
} finally {
outputStream.close();
}
}
public void save(OutputStream output) throws IOException {
DataOutputStream out = new DataOutputStream(output);
out.writeBytes(JADX_CLS_SET_HEADER);
out.writeByte(VERSION);
try {
out.writeBytes(JADX_CLS_SET_HEADER);
out.writeByte(VERSION);
LOG.info("Classes count: " + classes.length);
out.writeInt(classes.length);
for (NClass cls : classes) {
writeString(out, cls.getName());
}
for (NClass cls : classes) {
NClass[] parents = cls.getParents();
out.writeByte(parents.length);
for (NClass parent : parents) {
out.writeInt(parent.getId());
LOG.info("Classes count: {}", classes.length);
out.writeInt(classes.length);
for (NClass cls : classes) {
writeString(out, cls.getName());
}
for (NClass cls : classes) {
NClass[] parents = cls.getParents();
out.writeByte(parents.length);
for (NClass parent : parents) {
out.writeInt(parent.getId());
}
}
} finally {
out.close();
}
}
@@ -144,55 +154,67 @@ public class ClsSet {
if (input == null) {
throw new JadxRuntimeException("Can't load classpath file: " + CLST_FILENAME);
}
load(input);
try {
load(input);
} finally {
input.close();
}
}
public void load(File input) throws IOException, DecodeException {
String name = input.getName();
InputStream inputStream = new FileInputStream(input);
if (name.endsWith(CLST_EXTENSION)) {
load(inputStream);
} else if (name.endsWith(".jar")) {
ZipInputStream in = new ZipInputStream(inputStream);
try {
ZipEntry entry = in.getNextEntry();
while (entry != null) {
if (entry.getName().endsWith(CLST_EXTENSION)) {
load(in);
try {
if (name.endsWith(CLST_EXTENSION)) {
load(inputStream);
} else if (name.endsWith(".jar")) {
ZipInputStream in = new ZipInputStream(inputStream);
try {
ZipEntry entry = in.getNextEntry();
while (entry != null) {
if (entry.getName().endsWith(CLST_EXTENSION)) {
load(in);
}
entry = in.getNextEntry();
}
entry = in.getNextEntry();
} finally {
in.close();
}
} finally {
in.close();
} else {
throw new JadxRuntimeException("Unknown file format: " + name);
}
} else {
throw new JadxRuntimeException("Unknown file format: " + name);
} finally {
inputStream.close();
}
}
public void load(InputStream input) throws IOException, DecodeException {
DataInputStream in = new DataInputStream(input);
byte[] header = new byte[JADX_CLS_SET_HEADER.length()];
int readHeaderLength = in.read(header);
int version = in.readByte();
if (readHeaderLength != JADX_CLS_SET_HEADER.length()
|| !JADX_CLS_SET_HEADER.equals(new String(header, STRING_CHARSET))
|| version != VERSION) {
throw new DecodeException("Wrong jadx class set header");
}
int count = in.readInt();
classes = new NClass[count];
for (int i = 0; i < count; i++) {
String name = readString(in);
classes[i] = new NClass(name, i);
}
for (int i = 0; i < count; i++) {
int pCount = in.readByte();
NClass[] parents = new NClass[pCount];
for (int j = 0; j < pCount; j++) {
parents[j] = classes[in.readInt()];
try {
byte[] header = new byte[JADX_CLS_SET_HEADER.length()];
int readHeaderLength = in.read(header);
int version = in.readByte();
if (readHeaderLength != JADX_CLS_SET_HEADER.length()
|| !JADX_CLS_SET_HEADER.equals(new String(header, STRING_CHARSET))
|| version != VERSION) {
throw new DecodeException("Wrong jadx class set header");
}
classes[i].setParents(parents);
int count = in.readInt();
classes = new NClass[count];
for (int i = 0; i < count; i++) {
String name = readString(in);
classes[i] = new NClass(name, i);
}
for (int i = 0; i < count; i++) {
int pCount = in.readByte();
NClass[] parents = new NClass[pCount];
for (int j = 0; j < pCount; j++) {
parents[j] = classes[in.readInt()];
}
classes[i].setParents(parents);
}
} finally {
in.close();
}
}
@@ -77,15 +77,15 @@ public class ClspGraph {
return clsName;
}
NClass cls = nameMap.get(implClsName);
if (cls != null) {
if (isImplements(clsName, implClsName)) {
return implClsName;
}
Set<String> anc = getAncestors(clsName);
return searchCommonParent(anc, cls);
if (cls == null) {
LOG.debug("Missing class: {}", implClsName);
return null;
}
LOG.debug("Missing class: {}", implClsName);
return null;
if (isImplements(clsName, implClsName)) {
return implClsName;
}
Set<String> anc = getAncestors(clsName);
return searchCommonParent(anc, cls);
}
private String searchCommonParent(Set<String> anc, NClass cls) {
@@ -93,11 +93,10 @@ public class ClspGraph {
String name = p.getName();
if (anc.contains(name)) {
return name;
} else {
String r = searchCommonParent(anc, p);
if (r != null) {
return r;
}
}
String r = searchCommonParent(anc, p);
if (r != null) {
return r;
}
}
return null;
@@ -109,17 +108,17 @@ public class ClspGraph {
return result;
}
NClass cls = nameMap.get(clsName);
if (cls != null) {
result = new HashSet<String>();
addAncestorsNames(cls, result);
if (result.isEmpty()) {
result = Collections.emptySet();
}
ancestorCache.put(clsName, result);
return result;
if (cls == null) {
LOG.debug("Missing class: {}", clsName);
return Collections.emptySet();
}
LOG.debug("Missing class: {}", clsName);
return Collections.emptySet();
result = new HashSet<String>();
addAncestorsNames(cls, result);
if (result.isEmpty()) {
result = Collections.emptySet();
}
ancestorCache.put(clsName, result);
return result;
}
private void addAncestorsNames(NClass cls, Set<String> result) {
@@ -39,7 +39,7 @@ public class ConvertToClsSet {
}
}
for (InputFile inputFile : inputFiles) {
LOG.info("Loaded: " + inputFile.getFile());
LOG.info("Loaded: {}", inputFile.getFile());
}
RootNode root = new RootNode();
@@ -48,12 +48,15 @@ public class ConvertToClsSet {
ClsSet set = new ClsSet();
set.load(root);
set.save(output);
LOG.info("Output: " + output);
LOG.info("Output: {}", output);
LOG.info("done");
}
private static void addFilesFromDirectory(File dir, List<InputFile> inputFiles) throws IOException, DecodeException {
File[] files = dir.listFiles();
if (files == null) {
return;
}
for (File file : files) {
if (file.isDirectory()) {
addFilesFromDirectory(file, inputFiles);
@@ -1,7 +1,7 @@
package jadx.core.codegen;
import jadx.core.Consts;
import jadx.core.dex.attributes.AttributeType;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttributeNode;
import jadx.core.dex.attributes.annotations.Annotation;
import jadx.core.dex.attributes.annotations.AnnotationsList;
@@ -43,7 +43,7 @@ public class AnnotationGen {
public void addForParameter(CodeWriter code, MethodParameters paramsAnnotations, int n) {
AnnotationsList aList = paramsAnnotations.getParamList().get(n);
if (aList == null || aList.size() == 0) {
if (aList == null || aList.isEmpty()) {
return;
}
for (Annotation a : aList.getAll()) {
@@ -53,8 +53,8 @@ public class AnnotationGen {
}
private void add(IAttributeNode node, CodeWriter code) {
AnnotationsList aList = (AnnotationsList) node.getAttributes().get(AttributeType.ANNOTATION_LIST);
if (aList == null || aList.size() == 0) {
AnnotationsList aList = node.get(AType.ANNOTATION_LIST);
if (aList == null || aList.isEmpty()) {
return;
}
for (Annotation a : aList.getAll()) {
@@ -73,7 +73,7 @@ public class AnnotationGen {
private void formatAnnotation(CodeWriter code, Annotation a) {
code.add('@');
code.add(classGen.useClass(a.getType()));
classGen.useType(code, a.getType());
Map<String, Object> vl = a.getValues();
if (!vl.isEmpty()) {
code.add('(');
@@ -96,13 +96,13 @@ public class AnnotationGen {
@SuppressWarnings("unchecked")
public void addThrows(MethodNode mth, CodeWriter code) {
Annotation an = mth.getAttributes().getAnnotation(Consts.DALVIK_THROWS);
Annotation an = mth.getAnnotation(Consts.DALVIK_THROWS);
if (an != null) {
Object exs = an.getDefaultValue();
code.add(" throws ");
for (Iterator<ArgType> it = ((List<ArgType>) exs).iterator(); it.hasNext(); ) {
ArgType ex = it.next();
code.add(TypeGen.translate(classGen, ex));
classGen.useType(code, ex);
if (it.hasNext()) {
code.add(", ");
}
@@ -111,7 +111,7 @@ public class AnnotationGen {
}
public Object getAnnotationDefaultValue(String name) {
Annotation an = cls.getAttributes().getAnnotation(Consts.DALVIK_ANNOTATION_DEFAULT);
Annotation an = cls.getAnnotation(Consts.DALVIK_ANNOTATION_DEFAULT);
if (an != null) {
Annotation defAnnotation = (Annotation) an.getDefaultValue();
return defAnnotation.getValues().get(name);
@@ -144,14 +144,15 @@ public class AnnotationGen {
} else if (val instanceof Byte) {
code.add(TypeGen.formatByte((Byte) val));
} else if (val instanceof ArgType) {
code.add(TypeGen.translate(classGen, (ArgType) val)).add(".class");
classGen.useType(code, (ArgType) val);
code.add(".class");
} else if (val instanceof FieldInfo) {
// must be a static field
FieldInfo field = (FieldInfo) val;
code.add(InsnGen.makeStaticFieldAccess(field, classGen));
} else if (val instanceof List) {
InsnGen.makeStaticFieldAccess(code, field, classGen);
} else if (val instanceof Iterable) {
code.add('{');
Iterator<?> it = ((List) val).iterator();
Iterator<?> it = ((Iterable) val).iterator();
while (it.hasNext()) {
Object obj = it.next();
encodeValue(code, obj);
@@ -1,16 +1,18 @@
package jadx.core.codegen;
import jadx.api.IJadxArgs;
import jadx.core.Consts;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.AttrNode;
import jadx.core.dex.attributes.AttributeFlag;
import jadx.core.dex.attributes.AttributeType;
import jadx.core.dex.attributes.EnumClassAttr;
import jadx.core.dex.attributes.EnumClassAttr.EnumField;
import jadx.core.dex.attributes.SourceFileAttr;
import jadx.core.dex.attributes.nodes.EnumClassAttr;
import jadx.core.dex.attributes.nodes.EnumClassAttr.EnumField;
import jadx.core.dex.attributes.nodes.SourceFileAttr;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
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;
@@ -22,6 +24,7 @@ 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;
@@ -37,13 +40,27 @@ 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 boolean showInconsistentCode = false;
private final Set<ClassInfo> imports = new HashSet<ClassInfo>();
private int clsDeclLine = 0;
private int clsDeclLine;
public ClassGen(ClassNode cls, ClassGen parentClsGen, IJadxArgs jadxArgs) {
this(cls, parentClsGen, jadxArgs.isFallbackMode());
this.showInconsistentCode = jadxArgs.isShowInconsistentCode();
}
public ClassGen(ClassNode cls, ClassGen parentClsGen, boolean fallback) {
this.cls = cls;
@@ -62,12 +79,10 @@ public class ClassGen {
addClassCode(clsBody);
CodeWriter clsCode = new CodeWriter();
if (!"".equals(cls.getPackage())) {
clsCode.add("package ").add(cls.getPackage()).add(';');
clsCode.newLine();
}
int importsCount = imports.size();
if (importsCount != 0) {
List<String> sortImports = new ArrayList<String>(importsCount);
@@ -84,16 +99,15 @@ public class ClassGen {
sortImports.clear();
imports.clear();
}
clsCode.add(clsBody);
return clsCode;
}
public void addClassCode(CodeWriter code) throws CodegenException {
if (cls.getAttributes().contains(AttributeFlag.DONT_GENERATE)) {
if (cls.contains(AFlag.DONT_GENERATE)) {
return;
}
if (cls.getAttributes().contains(AttributeFlag.INCONSISTENT_CODE)) {
if (cls.contains(AFlag.INCONSISTENT_CODE)) {
code.startLine("// jadx: inconsistent code");
}
addClassDeclaration(code);
@@ -105,7 +119,14 @@ public class ClassGen {
if (af.isInterface()) {
af = af.remove(AccessFlags.ACC_ABSTRACT);
} else if (af.isEnum()) {
af = af.remove(AccessFlags.ACC_FINAL).remove(AccessFlags.ACC_ABSTRACT);
af = af.remove(AccessFlags.ACC_FINAL)
.remove(AccessFlags.ACC_ABSTRACT)
.remove(AccessFlags.ACC_STATIC);
}
// 'static' modifier not allowed for top classes (not inner)
if (!cls.getClassInfo().isInner()) {
af = af.remove(AccessFlags.ACC_STATIC);
}
annotationGen.addForClass(clsCode);
@@ -130,10 +151,12 @@ public class ClassGen {
if (sup != null
&& !sup.getFullName().equals(Consts.CLASS_OBJECT)
&& !sup.getFullName().equals(Consts.CLASS_ENUM)) {
clsCode.add("extends ").add(useClass(sup)).add(' ');
clsCode.add("extends ");
useClass(clsCode, sup);
clsCode.add(' ');
}
if (cls.getInterfaces().size() > 0 && !af.isAnnotation()) {
if (!cls.getInterfaces().isEmpty() && !af.isAnnotation()) {
if (cls.getAccessFlags().isInterface()) {
clsCode.add("extends ");
} else {
@@ -141,7 +164,7 @@ public class ClassGen {
}
for (Iterator<ClassInfo> it = cls.getInterfaces().iterator(); it.hasNext(); ) {
ClassInfo interf = it.next();
clsCode.add(useClass(interf));
useClass(clsCode, interf);
if (it.hasNext()) {
clsCode.add(", ");
}
@@ -165,12 +188,20 @@ public class ClassGen {
if (i != 0) {
code.add(", ");
}
code.add(useClass(type));
if (type.isGenericType()) {
code.add(type.getObject());
} else {
useClass(code, ClassInfo.fromType(type));
}
if (list != null && !list.isEmpty()) {
code.add(" extends ");
for (Iterator<ArgType> it = list.iterator(); it.hasNext(); ) {
ArgType g = it.next();
code.add(useClass(g));
if (g.isGenericType()) {
code.add(g.getObject());
} else {
useClass(code, ClassInfo.fromType(g));
}
if (it.hasNext()) {
code.add(" & ");
}
@@ -194,35 +225,63 @@ public class ClassGen {
}
private void addInnerClasses(CodeWriter code, ClassNode cls) throws CodegenException {
for (ClassNode innerCls : cls.getInnerClasses()) {
if (innerCls.contains(AFlag.DONT_GENERATE)
|| innerCls.isAnonymous()) {
continue;
}
ClassGen inClGen = new ClassGen(innerCls, getParentGen(), fallback);
code.newLine();
inClGen.addClassCode(code);
imports.addAll(inClGen.getImports());
}
}
private boolean isInnerClassesPresents() {
for (ClassNode innerCls : cls.getInnerClasses()) {
if (!innerCls.isAnonymous()) {
ClassGen inClGen = new ClassGen(innerCls, getParentGen(), fallback);
code.newLine();
inClGen.addClassCode(code);
imports.addAll(inClGen.getImports());
return true;
}
}
return false;
}
private void addMethods(CodeWriter code) {
for (MethodNode mth : cls.getMethods()) {
if (!mth.getAttributes().contains(AttributeFlag.DONT_GENERATE)) {
try {
if (code.getLine() != clsDeclLine) {
code.newLine();
}
addMethod(code, mth);
} catch (Exception e) {
String msg = ErrorsCounter.methodError(mth, "Method generation error", e);
code.startLine("/* " + msg + CodeWriter.NL + Utils.getStackTrace(e) + " */");
}
List<MethodNode> methods = sortMethodsByLine(cls.getMethods());
for (MethodNode mth : methods) {
if (mth.contains(AFlag.DONT_GENERATE)) {
continue;
}
if (code.getLine() != clsDeclLine) {
code.newLine();
}
try {
addMethod(code, mth);
} catch (Exception e) {
String msg = ErrorsCounter.methodError(mth, "Method generation error", e);
code.startLine("/* " + msg + CodeWriter.NL + Utils.getStackTrace(e) + " */");
}
}
}
private static List<MethodNode> sortMethodsByLine(List<MethodNode> methods) {
List<MethodNode> out = new ArrayList<MethodNode>(methods);
Collections.sort(out, METHOD_LINE_COMPARATOR);
return out;
}
private boolean isMethodsPresents() {
for (MethodNode mth : cls.getMethods()) {
if (!mth.contains(AFlag.DONT_GENERATE)) {
return true;
}
}
return false;
}
private void addMethod(CodeWriter code, MethodNode mth) throws CodegenException {
MethodGen mthGen = new MethodGen(this, mth);
if (mth.getAccessFlags().isAbstract() || mth.getAccessFlags().isNative()) {
MethodGen mthGen = new MethodGen(this, mth);
mthGen.addDefinition(code);
if (cls.getAccessFlags().isAnnotation()) {
Object def = annotationGen.getAnnotationDefaultValue(mth.getName());
@@ -233,11 +292,20 @@ public class ClassGen {
}
code.add(';');
} else {
boolean badCode = mth.getAttributes().contains(AttributeFlag.INCONSISTENT_CODE);
boolean badCode = mth.contains(AFlag.INCONSISTENT_CODE);
if (badCode) {
code.startLine("/* JADX WARNING: inconsistent code. */");
code.startLine("/* Code decompiled incorrectly, please refer to instructions dump. */");
LOG.error(ErrorsCounter.formatErrorMsg(mth, " Inconsistent code"));
ErrorsCounter.methodError(mth, "Inconsistent code");
if (showInconsistentCode) {
mth.remove(AFlag.INCONSISTENT_CODE);
}
}
MethodGen mthGen;
if (badCode || mth.contains(AType.JADX_ERROR)) {
mthGen = MethodGen.getFallbackMethodGen(mth);
} else {
mthGen = new MethodGen(this, mth);
}
if (mthGen.addDefinition(code)) {
code.add(' ');
@@ -254,15 +322,15 @@ public class ClassGen {
private void addFields(CodeWriter code) throws CodegenException {
addEnumFields(code);
for (FieldNode f : cls.getFields()) {
if (f.getAttributes().contains(AttributeFlag.DONT_GENERATE)) {
if (f.contains(AFlag.DONT_GENERATE)) {
continue;
}
annotationGen.addForField(code, f);
code.startLine(f.getAccessFlags().makeString());
code.add(TypeGen.translate(this, f.getType()));
useType(code, f.getType());
code.add(' ');
code.add(f.getName());
FieldValueAttr fv = (FieldValueAttr) f.getAttributes().get(AttributeType.FIELD_VALUE);
FieldValueAttr fv = f.get(AType.FIELD_VALUE);
if (fv != null) {
code.add(" = ");
if (fv.getValue() == null) {
@@ -276,82 +344,104 @@ public class ClassGen {
}
}
private void addEnumFields(CodeWriter code) throws CodegenException {
EnumClassAttr enumFields = (EnumClassAttr) cls.getAttributes().get(AttributeType.ENUM_CLASS);
if (enumFields != null) {
InsnGen igen = null;
for (Iterator<EnumField> it = enumFields.getFields().iterator(); it.hasNext(); ) {
EnumField f = it.next();
code.startLine(f.getName());
if (f.getArgs().size() != 0) {
code.add('(');
for (Iterator<InsnArg> aIt = f.getArgs().iterator(); aIt.hasNext(); ) {
InsnArg arg = aIt.next();
if (igen == null) {
// don't init mth gen if this is simple enum
MethodGen mthGen = new MethodGen(this, enumFields.getStaticMethod());
igen = new InsnGen(mthGen, false);
}
igen.addArg(code, arg);
if (aIt.hasNext()) {
code.add(", ");
}
}
code.add(')');
}
if (f.getCls() != null) {
new ClassGen(f.getCls(), this, fallback).addClassBody(code);
}
if (it.hasNext()) {
code.add(',');
}
private boolean isFieldsPresents() {
for (FieldNode field : cls.getFields()) {
if (!field.contains(AFlag.DONT_GENERATE)) {
return true;
}
}
return false;
}
private void addEnumFields(CodeWriter code) throws CodegenException {
EnumClassAttr enumFields = cls.get(AType.ENUM_CLASS);
if (enumFields == null) {
return;
}
InsnGen igen = null;
for (Iterator<EnumField> it = enumFields.getFields().iterator(); it.hasNext(); ) {
EnumField f = it.next();
code.startLine(f.getName());
if (!f.getArgs().isEmpty()) {
code.add('(');
for (Iterator<InsnArg> aIt = f.getArgs().iterator(); aIt.hasNext(); ) {
InsnArg arg = aIt.next();
if (igen == null) {
// don't init mth gen if this is simple enum
MethodGen mthGen = new MethodGen(this, enumFields.getStaticMethod());
igen = new InsnGen(mthGen, false);
}
igen.addArg(code, arg);
if (aIt.hasNext()) {
code.add(", ");
}
}
code.add(')');
}
if (f.getCls() != null) {
code.add(' ');
new ClassGen(f.getCls(), this, fallback).addClassBody(code);
}
if (it.hasNext()) {
code.add(',');
}
}
if (isMethodsPresents() || isFieldsPresents() || isInnerClassesPresents()) {
if (enumFields.getFields().isEmpty()) {
code.startLine();
}
code.add(';');
code.newLine();
}
}
public String useClass(ArgType clsType) {
if (clsType.isGenericType()) {
return clsType.getObject();
}
return useClass(ClassInfo.fromType(clsType));
}
public String useClass(ClassInfo classInfo) {
String baseClass = useClassInternal(cls.getClassInfo(), classInfo);
ArgType type = classInfo.getType();
ArgType[] generics = type.getGenericTypes();
if (generics == null) {
return baseClass;
}
StringBuilder sb = new StringBuilder();
sb.append(baseClass);
sb.append('<');
int len = generics.length;
for (int i = 0; i < len; i++) {
if (i != 0) {
sb.append(", ");
}
ArgType gt = generics[i];
ArgType wt = gt.getWildcardType();
if (wt != null) {
sb.append('?');
int bounds = gt.getWildcardBounds();
if (bounds != 0) {
sb.append(bounds == -1 ? " super " : " extends ");
sb.append(TypeGen.translate(this, wt));
}
public void useType(CodeWriter code, ArgType type) {
PrimitiveType stype = type.getPrimitiveType();
if (stype == null) {
code.add(type.toString());
} else if (stype == PrimitiveType.OBJECT) {
if (type.isGenericType()) {
code.add(type.getObject());
} else {
sb.append(TypeGen.translate(this, gt));
useClass(code, ClassInfo.fromType(type));
}
} else if (stype == PrimitiveType.ARRAY) {
useType(code, type.getArrayElement());
code.add("[]");
} else {
code.add(stype.getLongName());
}
}
public void useClass(CodeWriter code, ClassInfo classInfo) {
ClassNode classNode = cls.dex().resolveClass(classInfo);
if (classNode != null) {
code.attachAnnotation(classNode);
}
String baseClass = useClassInternal(cls.getClassInfo(), classInfo);
code.add(baseClass);
ArgType[] generics = classInfo.getType().getGenericTypes();
if (generics != null) {
code.add('<');
int len = generics.length;
for (int i = 0; i < len; i++) {
if (i != 0) {
code.add(", ");
}
ArgType gt = generics[i];
ArgType wt = gt.getWildcardType();
if (wt != null) {
code.add('?');
int bounds = gt.getWildcardBounds();
if (bounds != 0) {
code.add(bounds == -1 ? " super " : " extends ");
useType(code, wt);
}
} else {
useType(code, gt);
}
}
code.add('>');
}
sb.append('>');
return sb.toString();
}
private String useClassInternal(ClassInfo useCls, ClassInfo classInfo) {
@@ -376,7 +466,7 @@ public class ClassGen {
if (classNode != null && !classNode.getAccessFlags().isPublic()) {
return shortName;
}
if (searchCollision(cls.dex(), useCls, shortName)) {
if (searchCollision(cls.dex(), useCls, classInfo)) {
return fullName;
}
if (classInfo.getPackage().equals(useCls.getPackage())) {
@@ -422,26 +512,28 @@ public class ClassGen {
return false;
}
private static boolean searchCollision(DexNode dex, ClassInfo useCls, String shortName) {
private static boolean searchCollision(DexNode dex, ClassInfo useCls, ClassInfo searchCls) {
if (useCls == null) {
return false;
}
String shortName = searchCls.getShortName();
if (useCls.getShortName().equals(shortName)) {
return true;
}
ClassNode classNode = dex.resolveClass(useCls);
if (classNode != null) {
for (ClassNode inner : classNode.getInnerClasses()) {
if (inner.getShortName().equals(shortName)) {
if (inner.getShortName().equals(shortName)
&& !inner.getClassInfo().equals(searchCls)) {
return true;
}
}
}
return searchCollision(dex, useCls.getParentClass(), shortName);
return searchCollision(dex, useCls.getParentClass(), searchCls);
}
private void insertSourceFileInfo(CodeWriter code, AttrNode node) {
SourceFileAttr sourceFileAttr = (SourceFileAttr) node.getAttributes().get(AttributeType.SOURCE_FILE);
SourceFileAttr sourceFileAttr = node.get(AType.SOURCE_FILE);
if (sourceFileAttr != null) {
code.startLine("// compiled from: ").add(sourceFileAttr.getFileName());
}
@@ -15,15 +15,11 @@ public class CodeGen extends AbstractVisitor {
@Override
public boolean visit(ClassNode cls) throws CodegenException {
ClassGen clsGen = new ClassGen(cls, null, isFallbackMode());
ClassGen clsGen = new ClassGen(cls, null, args);
CodeWriter clsCode = clsGen.makeClass();
clsCode.finish();
cls.setCode(clsCode);
return false;
}
public boolean isFallbackMode() {
return args.isFallbackMode();
}
}
@@ -1,8 +1,8 @@
package jadx.core.codegen;
import jadx.api.CodePosition;
import jadx.core.dex.attributes.LineAttrNode;
import jadx.core.utils.Utils;
import jadx.core.dex.attributes.nodes.LineAttrNode;
import jadx.core.utils.files.FileUtils;
import java.io.File;
import java.io.PrintWriter;
@@ -10,6 +10,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -21,6 +22,8 @@ public class CodeWriter {
public static final String NL = System.getProperty("line.separator");
public static final String INDENT = " ";
private static final boolean ADD_LINE_NUMBERS = false;
private static final String[] INDENT_CACHE = {
"",
INDENT,
@@ -37,15 +40,14 @@ public class CodeWriter {
private int line = 1;
private int offset = 0;
private Map<CodePosition, Object> annotations = Collections.emptyMap();
private Map<Integer, Integer> lineMap = Collections.emptyMap();
public CodeWriter() {
this.indent = 0;
this.indentStr = "";
}
public CodeWriter(int indent) {
this.indent = indent;
updateIndent();
if (ADD_LINE_NUMBERS) {
incIndent(2);
}
}
public CodeWriter startLine() {
@@ -68,8 +70,23 @@ public class CodeWriter {
return this;
}
public CodeWriter add(Object obj) {
add(obj.toString());
public CodeWriter startLineWithNum(int sourceLine) {
if (sourceLine == 0) {
startLine();
return this;
}
if (ADD_LINE_NUMBERS) {
newLine();
attachSourceLine(sourceLine);
String ln = "/* " + sourceLine + " */ ";
add(ln);
if (indentStr.length() > ln.length()) {
add(indentStr.substring(ln.length()));
}
} else {
startLine();
attachSourceLine(sourceLine);
}
return this;
}
@@ -91,6 +108,9 @@ public class CodeWriter {
CodePosition pos = entry.getKey();
attachAnnotation(entry.getValue(), new CodePosition(line + pos.getLine(), pos.getOffset()));
}
for (Map.Entry<Integer, Integer> entry : code.lineMap.entrySet()) {
attachSourceLine(line + entry.getKey(), entry.getValue());
}
line += code.line;
offset = code.offset;
buf.append(code);
@@ -102,6 +122,11 @@ public class CodeWriter {
return this;
}
public CodeWriter addIndent() {
add(INDENT);
return this;
}
private void addLine() {
buf.append(NL);
line++;
@@ -114,11 +139,6 @@ public class CodeWriter {
return this;
}
public CodeWriter addIndent() {
add(INDENT);
return this;
}
private void updateIndent() {
int curIndent = indent;
if (curIndent < INDENT_CACHE.length) {
@@ -189,6 +209,24 @@ public class CodeWriter {
return annotations;
}
public void attachSourceLine(int sourceLine) {
if (sourceLine == 0) {
return;
}
attachSourceLine(line, sourceLine);
}
private void attachSourceLine(int decompiledLine, int sourceLine) {
if (lineMap.isEmpty()) {
lineMap = new TreeMap<Integer, Integer>();
}
lineMap.put(decompiledLine, sourceLine);
}
public Map<Integer, Integer> getLineMapping() {
return lineMap;
}
public void finish() {
buf.trimToSize();
Iterator<Map.Entry<CodePosition, Object>> it = annotations.entrySet().iterator();
@@ -206,9 +244,8 @@ public class CodeWriter {
private static String removeFirstEmptyLine(String str) {
if (str.startsWith(NL)) {
return str.substring(NL.length());
} else {
return str;
}
return str;
}
public int length() {
@@ -251,11 +288,11 @@ public class CodeWriter {
PrintWriter out = null;
try {
Utils.makeDirsForFile(file);
FileUtils.makeDirsForFile(file);
out = new PrintWriter(file, "UTF-8");
String code = buf.toString();
code = removeFirstEmptyLine(code);
out.print(code);
out.println(code);
} catch (Exception e) {
LOG.error("Save file error", e);
} finally {
@@ -8,13 +8,16 @@ import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.InsnWrapArg;
import jadx.core.dex.instructions.args.LiteralArg;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.regions.Compare;
import jadx.core.dex.regions.IfCondition;
import jadx.core.dex.regions.conditions.Compare;
import jadx.core.dex.regions.conditions.IfCondition;
import jadx.core.dex.regions.conditions.IfCondition.Mode;
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;
@@ -22,42 +25,83 @@ 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>();
public Queue<IfCondition> getStack() {
return stack;
}
public void push(IfCondition cond) {
stack.add(cond);
}
public IfCondition pop() {
return stack.poll();
}
}
public ConditionGen(InsnGen insnGen) {
super(insnGen.mgen, insnGen.fallback);
}
void add(CodeWriter code, IfCondition condition) throws CodegenException {
add(code, new CondStack(), condition);
}
void wrap(CodeWriter code, IfCondition condition) throws CodegenException {
wrap(code, new CondStack(), condition);
}
private void add(CodeWriter code, CondStack stack, IfCondition condition) throws CodegenException {
stack.push(condition);
switch (condition.getMode()) {
case COMPARE:
addCompare(code, condition.getCompare());
addCompare(code, stack, condition.getCompare());
break;
case TERNARY:
addTernary(code, stack, condition);
break;
case NOT:
addNot(code, condition);
addNot(code, stack, condition);
break;
case AND:
case OR:
addAndOr(code, condition);
addAndOr(code, stack, condition);
break;
default:
throw new JadxRuntimeException("Unknown condition mode: " + condition);
throw new JadxRuntimeException("Unknown condition mode: " + condition.getMode());
}
stack.pop();
}
void wrap(CodeWriter code, IfCondition cond) throws CodegenException {
private void wrap(CodeWriter code, CondStack stack, IfCondition cond) throws CodegenException {
boolean wrap = isWrapNeeded(cond);
if (wrap) {
code.add('(');
}
add(code, cond);
add(code, stack, cond);
if (wrap) {
code.add(')');
}
}
private void addCompare(CodeWriter code, Compare compare) throws CodegenException {
private void wrap(CodeWriter code, InsnArg firstArg) throws CodegenException {
boolean wrap = isArgWrapNeeded(firstArg);
if (wrap) {
code.add('(');
}
addArg(code, firstArg, false);
if (wrap) {
code.add(')');
}
}
private void addCompare(CodeWriter code, CondStack stack, Compare compare) throws CodegenException {
IfOp op = compare.getOp();
InsnArg firstArg = compare.getA();
InsnArg secondArg = compare.getB();
@@ -70,39 +114,44 @@ public class ConditionGen extends InsnGen {
}
if (op == IfOp.EQ) {
// == true
addArg(code, firstArg, false);
if (stack.getStack().size() == 1) {
addArg(code, firstArg, false);
} else {
wrap(code, firstArg);
}
return;
} else if (op == IfOp.NE) {
// != true
code.add('!');
boolean wrap = isWrapNeeded(firstArg);
if (wrap) {
code.add('(');
}
addArg(code, firstArg, false);
if (wrap) {
code.add(')');
}
wrap(code, firstArg);
return;
}
LOG.warn(ErrorsCounter.formatErrorMsg(mth, "Unsupported boolean condition " + op.getSymbol()));
}
addArg(code, firstArg, isWrapNeeded(firstArg));
addArg(code, firstArg, isArgWrapNeeded(firstArg));
code.add(' ').add(op.getSymbol()).add(' ');
addArg(code, secondArg, isWrapNeeded(secondArg));
addArg(code, secondArg, isArgWrapNeeded(secondArg));
}
private void addNot(CodeWriter code, IfCondition condition) throws CodegenException {
private void addTernary(CodeWriter code, CondStack stack, IfCondition condition) throws CodegenException {
add(code, stack, condition.first());
code.add(" ? ");
add(code, stack, condition.second());
code.add(" : ");
add(code, stack, condition.third());
}
private void addNot(CodeWriter code, CondStack stack, IfCondition condition) throws CodegenException {
code.add('!');
wrap(code, condition.getArgs().get(0));
wrap(code, stack, condition.getArgs().get(0));
}
private void addAndOr(CodeWriter code, IfCondition condition) throws CodegenException {
String mode = condition.getMode() == IfCondition.Mode.AND ? " && " : " || ";
private void addAndOr(CodeWriter code, CondStack stack, IfCondition condition) throws CodegenException {
String mode = condition.getMode() == Mode.AND ? " && " : " || ";
Iterator<IfCondition> it = condition.getArgs().iterator();
while (it.hasNext()) {
wrap(code, it.next());
wrap(code, stack, it.next());
if (it.hasNext()) {
code.add(mode);
}
@@ -110,15 +159,22 @@ public class ConditionGen extends InsnGen {
}
private boolean isWrapNeeded(IfCondition condition) {
return !condition.isCompare();
if (condition.isCompare()) {
return false;
}
if (condition.getMode() != Mode.NOT) {
return true;
}
return false;
}
private static boolean isWrapNeeded(InsnArg arg) {
private static boolean isArgWrapNeeded(InsnArg arg) {
if (!arg.isInsnWrap()) {
return false;
}
InsnNode insn = ((InsnWrapArg) arg).getWrapInsn();
if (insn.getType() == InsnType.ARITH) {
InsnType insnType = insn.getType();
if (insnType == InsnType.ARITH) {
switch (((ArithNode) insn).getOp()) {
case ADD:
case SUB:
@@ -127,8 +183,18 @@ public class ConditionGen extends InsnGen {
case REM:
return false;
}
} else if (insn.getType() == InsnType.INVOKE) {
return false;
} else {
switch (insnType) {
case INVOKE:
case SGET:
case IGET:
case AGET:
case CONST:
case ARRAY_LENGTH:
return false;
default:
return true;
}
}
return true;
}
@@ -1,10 +1,10 @@
package jadx.core.codegen;
import jadx.core.dex.attributes.AttributeFlag;
import jadx.core.dex.attributes.AttributeType;
import jadx.core.dex.attributes.FieldReplaceAttr;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.attributes.MethodInlineAttr;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
import jadx.core.dex.attributes.nodes.MethodInlineAttr;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.info.MethodInfo;
@@ -25,7 +25,7 @@ import jadx.core.dex.instructions.args.FieldArg;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.InsnWrapArg;
import jadx.core.dex.instructions.args.LiteralArg;
import jadx.core.dex.instructions.args.NamedArg;
import jadx.core.dex.instructions.args.Named;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.mods.ConstructorInsn;
import jadx.core.dex.instructions.mods.TernaryInsn;
@@ -47,7 +47,9 @@ import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -59,9 +61,10 @@ public class InsnGen {
protected final RootNode root;
protected final boolean fallback;
private static enum Flags {
protected enum Flags {
BODY_ONLY,
BODY_ONLY_NOWRAP,
INLINE
}
public InsnGen(MethodGen mgen, boolean fallback) {
@@ -89,22 +92,20 @@ public class InsnGen {
public void addArg(CodeWriter code, InsnArg arg, boolean wrap) throws CodegenException {
if (arg.isRegister()) {
code.add(mgen.makeArgName((RegisterArg) arg));
code.add(mgen.getNameGen().useArg((RegisterArg) arg));
} else if (arg.isLiteral()) {
code.add(lit((LiteralArg) arg));
} else if (arg.isInsnWrap()) {
Flags flag = wrap ? Flags.BODY_ONLY : Flags.BODY_ONLY_NOWRAP;
makeInsn(((InsnWrapArg) arg).getWrapInsn(), code, flag);
} else if (arg.isNamed()) {
code.add(((NamedArg) arg).getName());
code.add(((Named) arg).getName());
} else if (arg.isField()) {
FieldArg f = (FieldArg) arg;
if (f.isStatic()) {
code.add(staticField(f.getField()));
staticField(code, f.getField());
} else {
RegisterArg regArg = new RegisterArg(f.getRegNum());
regArg.replaceTypedVar(f);
instanceField(code, f.getField(), regArg);
instanceField(code, f.getField(), f.getInstanceArg());
}
} else {
throw new CodegenException("Unknown arg type " + arg);
@@ -113,7 +114,7 @@ public class InsnGen {
public void assignVar(CodeWriter code, InsnNode insn) throws CodegenException {
RegisterArg arg = insn.getResult();
if (insn.getAttributes().contains(AttributeFlag.DECLARE_VAR)) {
if (insn.contains(AFlag.DECLARE_VAR)) {
declareVar(code, arg);
} else {
addArg(code, arg, false);
@@ -121,9 +122,9 @@ public class InsnGen {
}
public void declareVar(CodeWriter code, RegisterArg arg) {
code.add(useType(arg.getType()));
useType(code, arg.getType());
code.add(' ');
code.add(mgen.assignArg(arg));
code.add(mgen.getNameGen().assignArg(arg));
}
private static String lit(LiteralArg arg) {
@@ -131,70 +132,92 @@ public class InsnGen {
}
private void instanceField(CodeWriter code, FieldInfo field, InsnArg arg) throws CodegenException {
FieldNode fieldNode = mth.getParentClass().searchField(field);
ClassNode pCls = mth.getParentClass();
FieldNode fieldNode = pCls.searchField(field);
while (fieldNode == null
&& pCls.getParentClass() != pCls
&& pCls.getParentClass() != null) {
pCls = pCls.getParentClass();
fieldNode = pCls.searchField(field);
}
if (fieldNode != null) {
FieldReplaceAttr replace = (FieldReplaceAttr) fieldNode.getAttributes().get(AttributeType.FIELD_REPLACE);
FieldReplaceAttr replace = fieldNode.get(AType.FIELD_REPLACE);
if (replace != null) {
FieldInfo info = replace.getFieldInfo();
if (replace.isOuterClass()) {
code.add(useClass(info.getDeclClass())).add(".this");
useClass(code, info.getDeclClass());
code.add(".this");
}
return;
}
}
addArgDot(code, arg);
fieldNode = mth.dex().resolveField(field);
if (fieldNode != null) {
code.attachAnnotation(fieldNode);
}
code.add(field.getName());
}
public static String makeStaticFieldAccess(FieldInfo field, ClassGen clsGen) {
public static void makeStaticFieldAccess(CodeWriter code, FieldInfo field, ClassGen clsGen) {
ClassInfo declClass = field.getDeclClass();
if (clsGen.getClassNode().getFullName().startsWith(declClass.getFullName())) {
return field.getName();
boolean fieldFromThisClass = clsGen.getClassNode().getFullName().startsWith(declClass.getFullName());
if (!fieldFromThisClass) {
// Android specific resources class handler
ClassInfo parentClass = declClass.getParentClass();
if (parentClass != null && parentClass.getShortName().equals("R")) {
clsGen.useClass(code, parentClass);
code.add('.');
code.add(declClass.getShortName());
} else {
clsGen.useClass(code, declClass);
}
code.add('.');
}
// Android specific resources class handler
ClassInfo parentClass = declClass.getParentClass();
if (parentClass != null && parentClass.getShortName().equals("R")) {
return clsGen.useClass(parentClass) + "." + declClass.getShortName() + "." + field.getName();
FieldNode fieldNode = clsGen.getClassNode().dex().resolveField(field);
if (fieldNode != null) {
code.attachAnnotation(fieldNode);
}
return clsGen.useClass(declClass) + '.' + field.getName();
code.add(field.getName());
}
protected String staticField(FieldInfo field) {
return makeStaticFieldAccess(field, mgen.getClassGen());
protected void staticField(CodeWriter code, FieldInfo field) {
makeStaticFieldAccess(code, field, mgen.getClassGen());
}
public String useClass(ClassInfo cls) {
return mgen.getClassGen().useClass(cls);
public void useClass(CodeWriter code, ClassInfo cls) {
mgen.getClassGen().useClass(code, cls);
}
private String useType(ArgType type) {
return TypeGen.translate(mgen.getClassGen(), type);
protected void useType(CodeWriter code, ArgType type) {
mgen.getClassGen().useType(code, type);
}
public boolean makeInsn(InsnNode insn, CodeWriter code) throws CodegenException {
return makeInsn(insn, code, null);
}
private boolean makeInsn(InsnNode insn, CodeWriter code, Flags flag) throws CodegenException {
protected boolean makeInsn(InsnNode insn, CodeWriter code, Flags flag) throws CodegenException {
try {
if (insn.getType() == InsnType.NOP) {
return false;
}
EnumSet<Flags> state = EnumSet.noneOf(Flags.class);
Set<Flags> state = EnumSet.noneOf(Flags.class);
if (flag == Flags.BODY_ONLY || flag == Flags.BODY_ONLY_NOWRAP) {
state.add(flag);
makeInsnBody(code, insn, state);
} else {
code.startLine();
if (insn.getSourceLine() != 0) {
code.attachAnnotation(insn.getSourceLine());
if (flag != Flags.INLINE) {
code.startLineWithNum(insn.getSourceLine());
}
if (insn.getResult() != null && insn.getType() != InsnType.ARITH_ONEARG) {
if (insn.getResult() != null && !insn.contains(AFlag.ARITH_ONEARG)) {
assignVar(code, insn);
code.add(" = ");
}
makeInsnBody(code, insn, state);
code.add(';');
if (flag != Flags.INLINE) {
code.add(';');
}
}
} catch (Throwable th) {
throw new CodegenException(mth, "Error generate insn: " + insn, th);
@@ -202,7 +225,7 @@ public class InsnGen {
return true;
}
private void makeInsnBody(CodeWriter code, InsnNode insn, EnumSet<Flags> state) throws CodegenException {
private void makeInsnBody(CodeWriter code, InsnNode insn, Set<Flags> state) throws CodegenException {
switch (insn.getType()) {
case CONST_STR:
String str = ((ConstStringNode) insn).getString();
@@ -211,7 +234,8 @@ public class InsnGen {
case CONST_CLASS:
ArgType clsType = ((ConstClassNode) insn).getClsType();
code.add(useType(clsType)).add(".class");
useType(code, clsType);
code.add(".class");
break;
case CONST:
@@ -230,7 +254,7 @@ public class InsnGen {
code.add('(');
}
code.add('(');
code.add(useType((ArgType) ((IndexInsnNode) insn).getIndex()));
useType(code, (ArgType) ((IndexInsnNode) insn).getIndex());
code.add(") ");
addArg(code, insn.getArg(0), true);
if (wrap) {
@@ -243,10 +267,6 @@ public class InsnGen {
makeArith((ArithNode) insn, code, state);
break;
case ARITH_ONEARG:
makeArithOneArg((ArithNode) insn, code, state);
break;
case NEG: {
boolean wrap = state.contains(Flags.BODY_ONLY);
if (wrap) {
@@ -271,6 +291,10 @@ public class InsnGen {
case BREAK:
code.add("break");
LoopLabelAttr labelAttr = insn.get(AType.LOOP_LABEL);
if (labelAttr != null) {
code.add(' ').add(mgen.getNameGen().getLoopLabel(labelAttr));
}
break;
case CONTINUE:
@@ -292,7 +316,7 @@ public class InsnGen {
addArg(code, insn.getArg(0));
code.add(" == ");
addArg(code, insn.getArg(1));
code.add("? 0 : -1))");
code.add(" ? 0 : -1))");
break;
case INSTANCE_OF: {
@@ -302,14 +326,14 @@ public class InsnGen {
}
addArg(code, insn.getArg(0));
code.add(" instanceof ");
code.add(useType((ArgType) ((IndexInsnNode) insn).getIndex()));
useType(code, (ArgType) ((IndexInsnNode) insn).getIndex());
if (wrap) {
code.add(')');
}
break;
}
case CONSTRUCTOR:
makeConstructor((ConstructorInsn) insn, code, state);
makeConstructor((ConstructorInsn) insn, code);
break;
case INVOKE:
@@ -318,7 +342,8 @@ public class InsnGen {
case NEW_ARRAY: {
ArgType arrayType = insn.getResult().getType();
code.add("new ").add(useType(arrayType.getArrayRootElement()));
code.add("new ");
useType(code, arrayType.getArrayRootElement());
code.add('[');
addArg(code, insn.getArg(0));
code.add(']');
@@ -371,11 +396,12 @@ public class InsnGen {
}
case SGET:
code.add(staticField((FieldInfo) ((IndexInsnNode) insn).getIndex()));
staticField(code, (FieldInfo) ((IndexInsnNode) insn).getIndex());
break;
case SPUT:
FieldInfo field = (FieldInfo) ((IndexInsnNode) insn).getIndex();
code.add(staticField(field)).add(" = ");
staticField(code, field);
code.add(" = ");
addArg(code, insn.getArg(0), false);
break;
@@ -411,22 +437,17 @@ public class InsnGen {
}
break;
case MOVE_EXCEPTION:
if (isFallback()) {
code.add("move-exception");
} else {
addArg(code, insn.getArg(0));
}
break;
case TERNARY:
makeTernary((TernaryInsn) insn, code, state);
break;
case ARGS:
case ONE_ARG:
addArg(code, insn.getArg(0));
break;
case PHI:
break;
/* fallback mode instructions */
case IF:
assert isFallback() : "if insn in not fallback mode";
@@ -444,6 +465,11 @@ public class InsnGen {
code.add("goto ").add(MethodGen.getLabelName(((GotoNode) insn).getTarget()));
break;
case MOVE_EXCEPTION:
assert isFallback();
code.add("move-exception");
break;
case SWITCH:
assert isFallback();
SwitchNode sw = (SwitchNode) insn;
@@ -452,7 +478,8 @@ public class InsnGen {
code.add(") {");
code.incIndent();
for (int i = 0; i < sw.getCasesCount(); i++) {
code.startLine("case ").add(sw.getKeys()[i]).add(": goto ");
String key = sw.getKeys()[i].toString();
code.startLine("case ").add(key).add(": goto ");
code.add(MethodGen.getLabelName(sw.getTargets()[i])).add(';');
}
code.startLine("default: goto ");
@@ -474,10 +501,11 @@ public class InsnGen {
private void filledNewArray(InsnNode insn, CodeWriter code) throws CodegenException {
int c = insn.getArgsCount();
code.add("new ").add(useType(insn.getResult().getType()));
code.add("new ");
useType(code, insn.getResult().getType());
code.add('{');
for (int i = 0; i < c; i++) {
addArg(code, insn.getArg(i));
addArg(code, insn.getArg(i), false);
if (i + 1 < c) {
code.add(", ");
}
@@ -486,6 +514,13 @@ public class InsnGen {
}
private void fillArray(FillArrayNode insn, CodeWriter code) throws CodegenException {
String filledArray = makeArrayElements(insn);
code.add("new ");
useType(code, insn.getElementType());
code.add("[]{").add(filledArray).add('}');
}
private String makeArrayElements(FillArrayNode insn) throws CodegenException {
ArgType insnArrayType = insn.getResult().getType();
ArgType insnElementType = insnArrayType.getArrayElement();
ArgType elType = insn.getElementType();
@@ -494,10 +529,13 @@ public class InsnGen {
"Incorrect type for fill-array insn " + InsnUtils.formatOffset(insn.getOffset())
+ ", element type: " + elType + ", insn element type: " + insnElementType
);
if (!elType.isTypeKnown()) {
elType = insnElementType.isTypeKnown() ? insnElementType : elType.selectFirst();
}
}
if (!elType.isTypeKnown()) {
LOG.warn("Unknown array element type: {} in mth: {}", elType, mth);
elType = insnElementType.isTypeKnown() ? insnElementType : elType.selectFirst();
}
insn.mergeElementType(elType);
StringBuilder str = new StringBuilder();
Object data = insn.getData();
switch (elType.getPrimitiveType()) {
@@ -539,13 +577,13 @@ public class InsnGen {
}
int len = str.length();
str.delete(len - 2, len);
code.add("new ").add(useType(elType)).add("[]{").add(str.toString()).add('}');
return str.toString();
}
private void makeConstructor(ConstructorInsn insn, CodeWriter code, EnumSet<Flags> state)
private void makeConstructor(ConstructorInsn insn, CodeWriter code)
throws CodegenException {
ClassNode cls = mth.dex().resolveClass(insn.getClassType());
if (cls != null && cls.isAnonymous()) {
if (cls != null && cls.isAnonymous() && !fallback) {
// anonymous class construction
ClassInfo parent;
if (cls.getInterfaces().size() == 1) {
@@ -553,14 +591,22 @@ public class InsnGen {
} else {
parent = cls.getSuperClass();
}
cls.getAttributes().add(AttributeFlag.DONT_GENERATE);
cls.add(AFlag.DONT_GENERATE);
MethodNode defCtr = cls.getDefaultConstructor();
if (RegionUtils.notEmpty(defCtr.getRegion())) {
defCtr.getAttributes().add(AttributeFlag.ANONYMOUS_CONSTRUCTOR);
} else {
defCtr.getAttributes().add(AttributeFlag.DONT_GENERATE);
if (defCtr != null) {
if (RegionUtils.notEmpty(defCtr.getRegion())) {
defCtr.add(AFlag.ANONYMOUS_CONSTRUCTOR);
} else {
defCtr.add(AFlag.DONT_GENERATE);
}
}
code.add("new ").add(parent == null ? "Object" : useClass(parent)).add("() ");
code.add("new ");
if (parent == null) {
code.add("Object");
} else {
useClass(code, parent);
}
code.add("() ");
new ClassGen(cls, mgen.getClassGen().getParentGen(), fallback).addClassBody(code);
return;
}
@@ -572,9 +618,10 @@ public class InsnGen {
} else if (insn.isThis()) {
code.add("this");
} else {
code.add("new ").add(useClass(insn.getClassType()));
code.add("new ");
useClass(code, insn.getClassType());
}
generateArguments(code, insn, 0, mth.dex().resolveMethod(insn.getCallMth()));
generateMethodArguments(code, insn, 0, mth.dex().resolveMethod(insn.getCallMth()));
}
private void makeInvoke(InvokeNode insn, CodeWriter code) throws CodegenException {
@@ -582,9 +629,7 @@ public class InsnGen {
// inline method
MethodNode callMthNode = mth.dex().resolveMethod(callMth);
if (callMthNode != null
&& callMthNode.getAttributes().contains(AttributeType.METHOD_INLINE)) {
inlineMethod(callMthNode, insn, code);
if (callMthNode != null && inlineMethod(callMthNode, insn, code)) {
return;
}
@@ -612,7 +657,8 @@ public class InsnGen {
ClassInfo insnCls = mth.getParentClass().getClassInfo();
ClassInfo declClass = callMth.getDeclClass();
if (!insnCls.equals(declClass)) {
code.add(useClass(declClass)).add('.');
useClass(code, declClass);
code.add('.');
}
break;
}
@@ -620,50 +666,82 @@ public class InsnGen {
code.attachAnnotation(callMthNode);
}
code.add(callMth.getName());
generateArguments(code, insn, k, callMthNode);
generateMethodArguments(code, insn, k, callMthNode);
}
private void generateArguments(CodeWriter code, InsnNode insn, int k, MethodNode callMth) throws CodegenException {
if (callMth != null && callMth.getAttributes().contains(AttributeFlag.SKIP_FIRST_ARG)) {
private void generateMethodArguments(CodeWriter code, InsnNode insn, int startArgNum,
@Nullable MethodNode callMth) throws CodegenException {
int k = startArgNum;
if (callMth != null && callMth.contains(AFlag.SKIP_FIRST_ARG)) {
k++;
}
int argsCount = insn.getArgsCount();
if (callMth != null && callMth.isArgsOverload()) {
// add additional argument casts for overloaded methods
List<ArgType> originalType = callMth.getMethodInfo().getArgumentsTypes();
int origPos = 0;
code.add('(');
code.add('(');
if (k < argsCount) {
boolean overloaded = callMth != null && callMth.isArgsOverload();
for (int i = k; i < argsCount; i++) {
InsnArg arg = insn.getArg(i);
ArgType origType = originalType.get(origPos);
if (!arg.getType().equals(origType)) {
code.add('(').add(useType(origType)).add(')');
addArg(code, arg, true);
} else {
addArg(code, arg, false);
boolean cast = overloaded && processOverloadedArg(code, callMth, arg, i - startArgNum);
if (!cast && i == argsCount - 1 && processVarArg(code, callMth, arg)) {
continue;
}
addArg(code, arg, false);
if (i < argsCount - 1) {
code.add(", ");
}
origPos++;
}
code.add(')');
} else {
code.add('(');
if (k < argsCount) {
addArg(code, insn.getArg(k), false);
for (int i = k + 1; i < argsCount; i++) {
code.add(", ");
addArg(code, insn.getArg(i), false);
}
}
code.add(')');
}
code.add(')');
}
private void inlineMethod(MethodNode callMthNode, InvokeNode insn, CodeWriter code) throws CodegenException {
IAttribute mia = callMthNode.getAttributes().get(AttributeType.METHOD_INLINE);
InsnNode inl = ((MethodInlineAttr) mia).getInsn();
/**
* Add additional cast for overloaded method argument.
*/
private boolean processOverloadedArg(CodeWriter code, MethodNode callMth, InsnArg arg, int origPos) {
ArgType origType = callMth.getMethodInfo().getArgumentsTypes().get(origPos);
if (!arg.getType().equals(origType)) {
code.add('(');
useType(code, origType);
code.add(") ");
return true;
}
return false;
}
/**
* Expand varArgs from filled array.
*/
private boolean processVarArg(CodeWriter code, MethodNode callMth, InsnArg lastArg) throws CodegenException {
if (callMth == null || !callMth.getAccessFlags().isVarArgs()) {
return false;
}
if (!lastArg.getType().isArray() || !lastArg.isInsnWrap()) {
return false;
}
InsnNode insn = ((InsnWrapArg) lastArg).getWrapInsn();
if (insn.getType() == InsnType.FILLED_NEW_ARRAY) {
int count = insn.getArgsCount();
for (int i = 0; i < count; i++) {
InsnArg elemArg = insn.getArg(i);
addArg(code, elemArg, false);
if (i < count - 1) {
code.add(", ");
}
}
return true;
} else if (insn.getType() == InsnType.FILL_ARRAY) {
code.add(makeArrayElements((FillArrayNode) insn));
return true;
}
return false;
}
private boolean inlineMethod(MethodNode callMthNode, InvokeNode insn, CodeWriter code) throws CodegenException {
MethodInlineAttr mia = callMthNode.get(AType.METHOD_INLINE);
if (mia == null) {
return false;
}
InsnNode inl = mia.getInsn();
if (callMthNode.getMethodInfo().getArgumentsTypes().isEmpty()) {
makeInsn(inl, code, Flags.BODY_ONLY);
} else {
@@ -680,12 +758,13 @@ public class InsnGen {
inl.getRegisterArgs(inlArgs);
Map<RegisterArg, InsnArg> toRevert = new HashMap<RegisterArg, InsnArg>();
for (RegisterArg r : inlArgs) {
if (r.getRegNum() >= regs.length) {
LOG.warn("Unknown register number {} in method call: {}, {}", r, callMthNode, mth);
int regNum = r.getRegNum();
if (regNum >= regs.length) {
LOG.warn("Unknown register number {} in method call: {} from {}", r, callMthNode, mth);
} else {
InsnArg repl = regs[r.getRegNum()];
InsnArg repl = regs[regNum];
if (repl == null) {
LOG.warn("Not passed register {} in method call: {}, {}", r, callMthNode, mth);
LOG.warn("Not passed register {} in method call: {} from {}", r, callMthNode, mth);
} else {
inl.replaceArg(r, repl);
toRevert.put(r, repl);
@@ -693,14 +772,15 @@ public class InsnGen {
}
}
makeInsn(inl, code, Flags.BODY_ONLY);
// revert changes
// revert changes in 'MethodInlineAttr'
for (Map.Entry<RegisterArg, InsnArg> e : toRevert.entrySet()) {
inl.replaceArg(e.getValue(), e.getKey());
}
}
return true;
}
private void makeTernary(TernaryInsn insn, CodeWriter code, EnumSet<Flags> state) throws CodegenException {
private void makeTernary(TernaryInsn insn, CodeWriter code, Set<Flags> state) throws CodegenException {
boolean wrap = state.contains(Flags.BODY_ONLY);
if (wrap) {
code.add('(');
@@ -722,10 +802,13 @@ public class InsnGen {
}
}
private void makeArith(ArithNode insn, CodeWriter code, EnumSet<Flags> state) throws CodegenException {
private void makeArith(ArithNode insn, CodeWriter code, Set<Flags> state) throws CodegenException {
if (insn.contains(AFlag.ARITH_ONEARG)) {
makeArithOneArg(insn, code);
return;
}
// wrap insn in brackets for save correct operation order
boolean wrap = state.contains(Flags.BODY_ONLY)
&& !insn.getAttributes().contains(AttributeFlag.DONT_WRAP);
boolean wrap = state.contains(Flags.BODY_ONLY) && !insn.contains(AFlag.DONT_WRAP);
if (wrap) {
code.add('(');
}
@@ -739,9 +822,9 @@ public class InsnGen {
}
}
private void makeArithOneArg(ArithNode insn, CodeWriter code, EnumSet<Flags> state) throws CodegenException {
private void makeArithOneArg(ArithNode insn, CodeWriter code) throws CodegenException {
ArithOp op = insn.getOp();
InsnArg arg = insn.getArg(0);
InsnArg arg = insn.getArg(1);
// "++" or "--"
if (arg.isLiteral() && (op == ArithOp.ADD || op == ArithOp.SUB)) {
LiteralArg lit = (LiteralArg) arg;
@@ -1,18 +1,14 @@
package jadx.core.codegen;
import jadx.core.Consts;
import jadx.core.dex.attributes.AttributeFlag;
import jadx.core.dex.attributes.AttributeType;
import jadx.core.dex.attributes.AttributesList;
import jadx.core.dex.attributes.JadxErrorAttr;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.annotations.MethodParameters;
import jadx.core.dex.attributes.nodes.JadxErrorAttr;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.NamedArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.regions.Region;
import jadx.core.dex.trycatch.CatchAttr;
import jadx.core.dex.visitors.DepthTraversal;
import jadx.core.dex.visitors.FallbackModeVisitor;
@@ -22,10 +18,8 @@ import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.CodegenException;
import jadx.core.utils.exceptions.DecodeException;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -37,27 +31,24 @@ public class MethodGen {
private final MethodNode mth;
private final ClassGen classGen;
private final boolean fallback;
private final AnnotationGen annotationGen;
private final Set<String> varNames = new HashSet<String>();
private final NameGen nameGen;
public MethodGen(ClassGen classGen, MethodNode mth) {
this.mth = mth;
this.classGen = classGen;
this.fallback = classGen.isFallbackMode();
this.annotationGen = classGen.getAnnotationGen();
List<RegisterArg> args = mth.getArguments(true);
for (RegisterArg arg : args) {
varNames.add(makeArgName(arg));
}
this.nameGen = new NameGen(classGen.isFallbackMode());
}
public ClassGen getClassGen() {
return classGen;
}
public NameGen getNameGen() {
return nameGen;
}
public MethodNode getMethodNode() {
return mth;
}
@@ -68,7 +59,7 @@ public class MethodGen {
code.attachDefinition(mth);
return true;
}
if (mth.getAttributes().contains(AttributeFlag.ANONYMOUS_CONSTRUCTOR)) {
if (mth.contains(AFlag.ANONYMOUS_CONSTRUCTOR)) {
// don't add method name and arguments
code.startLine();
code.attachDefinition(mth);
@@ -78,15 +69,17 @@ public class MethodGen {
AccessInfo clsAccFlags = mth.getParentClass().getAccessFlags();
AccessInfo ai = mth.getAccessFlags();
// don't add 'abstract' to methods in interface
// don't add 'abstract' and 'public' to methods in interface
if (clsAccFlags.isInterface()) {
ai = ai.remove(AccessFlags.ACC_ABSTRACT);
ai = ai.remove(AccessFlags.ACC_PUBLIC);
}
// don't add 'public' for annotations
if (clsAccFlags.isAnnotation()) {
ai = ai.remove(AccessFlags.ACC_PUBLIC);
}
code.startLine(ai.makeString());
code.startLineWithNum(mth.getSourceLine());
code.add(ai.makeString());
if (classGen.addGenericMap(code, mth.getGenericMap())) {
code.add(' ');
@@ -94,7 +87,7 @@ public class MethodGen {
if (mth.getAccessFlags().isConstructor()) {
code.add(classGen.getClassNode().getShortName()); // constructor
} else {
code.add(TypeGen.translate(classGen, mth.getReturnType()));
classGen.useType(code, mth.getReturnType());
code.add(' ');
code.add(mth.getName());
}
@@ -102,7 +95,7 @@ public class MethodGen {
List<RegisterArg> args = mth.getArguments(false);
if (mth.getMethodInfo().isConstructor()
&& mth.getParentClass().getAttributes().contains(AttributeType.ENUM_CLASS)) {
&& mth.getParentClass().contains(AType.ENUM_CLASS)) {
if (args.size() == 2) {
args.clear();
} else if (args.size() > 2) {
@@ -123,9 +116,7 @@ public class MethodGen {
}
private void addMethodArguments(CodeWriter argsCode, List<RegisterArg> args) {
MethodParameters paramsAnnotation =
(MethodParameters) mth.getAttributes().get(AttributeType.ANNOTATION_MTH_PARAMETERS);
MethodParameters paramsAnnotation = mth.get(AType.ANNOTATION_MTH_PARAMETERS);
int i = 0;
for (Iterator<RegisterArg> it = args.iterator(); it.hasNext(); ) {
RegisterArg arg = it.next();
@@ -139,17 +130,17 @@ public class MethodGen {
ArgType type = arg.getType();
if (type.isArray()) {
ArgType elType = type.getArrayElement();
argsCode.add(TypeGen.translate(classGen, elType));
argsCode.add(" ...");
classGen.useType(argsCode, elType);
argsCode.add("...");
} else {
LOG.warn(ErrorsCounter.formatErrorMsg(mth, "Last argument in varargs method not array"));
argsCode.add(TypeGen.translate(classGen, arg.getType()));
classGen.useType(argsCode, arg.getType());
}
} else {
argsCode.add(TypeGen.translate(classGen, arg.getType()));
classGen.useType(argsCode, arg.getType());
}
argsCode.add(' ');
argsCode.add(makeArgName(arg));
argsCode.add(nameGen.assignArg(arg));
i++;
if (it.hasNext()) {
@@ -158,117 +149,34 @@ public class MethodGen {
}
}
/**
* Make variable name for register,
* Name contains register number and
* variable type or name (if debug info available)
*/
public String makeArgName(RegisterArg arg) {
String name = arg.getTypedVar().getName();
String base = "r" + arg.getRegNum();
if (fallback) {
if (name != null) {
return base + "_" + name;
}
return base;
} else {
if (name != null) {
if (Consts.DEBUG) {
return base + "_" + name;
}
return name;
} else {
ArgType type = arg.getType();
if (type.isPrimitive()) {
return base + type.getPrimitiveType().getShortName().toLowerCase();
} else {
return base + "_" + Utils.escape(TypeGen.translate(classGen, arg.getType()));
}
}
}
}
/**
* Put variable declaration and return variable name (used for assignments)
*
* @param arg register variable
* @return variable name
*/
public String assignArg(RegisterArg arg) {
String name = makeArgName(arg);
if (varNames.add(name) || fallback) {
return name;
}
name = getUniqVarName(name);
arg.getTypedVar().setName(name);
return name;
}
public String assignNamedArg(NamedArg arg) {
String name = arg.getName();
if (varNames.add(name) || fallback) {
return name;
}
name = getUniqVarName(name);
arg.setName(name);
return name;
}
private String getUniqVarName(String name) {
String r;
int i = 2;
do {
r = name + "_" + i;
i++;
} while (varNames.contains(r));
varNames.add(r);
return r;
}
public void addInstructions(CodeWriter code) throws CodegenException {
if (mth.getAttributes().contains(AttributeType.JADX_ERROR)) {
code.startLine("throw new UnsupportedOperationException(\"Method not decompiled: ");
code.add(mth.toString());
code.add("\");");
if (mth.contains(AType.JADX_ERROR)
|| mth.contains(AFlag.INCONSISTENT_CODE)
|| mth.getRegion() == null) {
code.startLine("throw new UnsupportedOperationException(\"Method not decompiled: ")
.add(mth.toString())
.add("\");");
JadxErrorAttr err = (JadxErrorAttr) mth.getAttributes().get(AttributeType.JADX_ERROR);
code.startLine("/* JADX: method processing error */");
Throwable cause = err.getCause();
if (cause != null) {
code.newLine();
code.add("/*");
code.startLine("Error: ").add(Utils.getStackTrace(cause));
code.add("*/");
if (mth.contains(AType.JADX_ERROR)) {
JadxErrorAttr err = mth.get(AType.JADX_ERROR);
code.startLine("/* JADX: method processing error */");
Throwable cause = err.getCause();
if (cause != null) {
code.newLine();
code.add("/*");
code.startLine("Error: ").add(Utils.getStackTrace(cause));
code.add("*/");
}
}
makeMethodDump(code);
} else if (mth.getAttributes().contains(AttributeFlag.INCONSISTENT_CODE)) {
code.startLine("/*");
addFallbackMethodCode(code);
code.startLine("*/");
code.newLine();
} else {
Region startRegion = mth.getRegion();
if (startRegion != null) {
(new RegionGen(this)).makeRegion(code, startRegion);
} else {
addFallbackMethodCode(code);
}
RegionGen regionGen = new RegionGen(this);
regionGen.makeRegion(code, mth.getRegion());
}
}
private void makeMethodDump(CodeWriter code) {
code.startLine("/*");
getFallbackMethodGen(mth).addDefinition(code);
code.add(" {");
code.incIndent();
addFallbackMethodCode(code);
code.decIndent();
code.startLine('}');
code.startLine("*/");
}
public void addFallbackMethodCode(CodeWriter code) {
if (mth.getInstructions() == null) {
// load original instructions
@@ -281,37 +189,37 @@ public class MethodGen {
return;
}
}
List<InsnNode> insns = mth.getInstructions();
if (insns == null) {
InsnNode[] insnArr = mth.getInstructions();
if (insnArr == null) {
code.startLine("// Can't load method instructions.");
return;
}
if (mth.getThisArg() != null) {
code.startLine(getFallbackMethodGen(mth).makeArgName(mth.getThisArg())).add(" = this;");
code.startLine(nameGen.useArg(mth.getThisArg())).add(" = this;");
}
addFallbackInsns(code, mth, insns, true);
addFallbackInsns(code, mth, insnArr, true);
}
public static void addFallbackInsns(CodeWriter code, MethodNode mth, List<InsnNode> insns, boolean addLabels) {
public static void addFallbackInsns(CodeWriter code, MethodNode mth, InsnNode[] insnArr, boolean addLabels) {
InsnGen insnGen = new InsnGen(getFallbackMethodGen(mth), true);
for (InsnNode insn : insns) {
AttributesList attrs = insn.getAttributes();
if (addLabels) {
if (attrs.contains(AttributeType.JUMP)
|| attrs.contains(AttributeType.EXC_HANDLER)) {
code.decIndent();
code.startLine(getLabelName(insn.getOffset()) + ":");
code.incIndent();
}
for (InsnNode insn : insnArr) {
if (insn == null) {
continue;
}
if (addLabels && (insn.contains(AType.JUMP) || insn.contains(AType.EXC_HANDLER))) {
code.decIndent();
code.startLine(getLabelName(insn.getOffset()) + ":");
code.incIndent();
}
try {
if (insnGen.makeInsn(insn, code)) {
CatchAttr catchAttr = (CatchAttr) attrs.get(AttributeType.CATCH_BLOCK);
CatchAttr catchAttr = insn.get(AType.CATCH_BLOCK);
if (catchAttr != null) {
code.add("\t //" + catchAttr);
code.add("\t " + catchAttr);
}
}
} catch (CodegenException e) {
LOG.debug("Error generate fallback instruction: ", e.getCause());
code.startLine("// error: " + insn);
}
}
@@ -320,7 +228,7 @@ public class MethodGen {
/**
* Return fallback variant of method codegen
*/
private static MethodGen getFallbackMethodGen(MethodNode mth) {
static MethodGen getFallbackMethodGen(MethodNode mth) {
ClassGen clsGen = new ClassGen(mth.getParentClass(), null, true);
return new MethodGen(clsGen, mth);
}
@@ -0,0 +1,230 @@
package jadx.core.codegen;
import jadx.core.Consts;
import jadx.core.deobf.NameMapper;
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.instructions.InvokeNode;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.InsnWrapArg;
import jadx.core.dex.instructions.args.NamedArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.args.SSAVar;
import jadx.core.dex.instructions.mods.ConstructorInsn;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.utils.Utils;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
public class NameGen {
private static final Map<String, String> OBJ_ALIAS;
private final Set<String> varNames = new HashSet<String>();
private final boolean fallback;
static {
OBJ_ALIAS = new HashMap<String, String>();
OBJ_ALIAS.put(Consts.CLASS_STRING, "str");
OBJ_ALIAS.put(Consts.CLASS_CLASS, "cls");
OBJ_ALIAS.put(Consts.CLASS_THROWABLE, "th");
OBJ_ALIAS.put(Consts.CLASS_OBJECT, "obj");
OBJ_ALIAS.put("java.util.Iterator", "it");
OBJ_ALIAS.put("java.lang.Boolean", "bool");
OBJ_ALIAS.put("java.lang.Short", "sh");
OBJ_ALIAS.put("java.lang.Integer", "num");
OBJ_ALIAS.put("java.lang.Character", "ch");
OBJ_ALIAS.put("java.lang.Byte", "b");
OBJ_ALIAS.put("java.lang.Float", "f");
OBJ_ALIAS.put("java.lang.Long", "l");
OBJ_ALIAS.put("java.lang.Double", "d");
}
public NameGen(boolean fallback) {
this.fallback = fallback;
}
public String assignArg(RegisterArg arg) {
String name = makeArgName(arg);
if (fallback) {
return name;
}
name = getUniqueVarName(name);
arg.setName(name);
return name;
}
public String assignNamedArg(NamedArg arg) {
String name = arg.getName();
if (fallback) {
return name;
}
name = getUniqueVarName(name);
arg.setName(name);
return name;
}
public String useArg(RegisterArg arg) {
String name = arg.getName();
if (name == null) {
return getFallbackName(arg);
}
return name;
}
// TODO: avoid name collision with variables names
public String getLoopLabel(LoopLabelAttr attr) {
String name = "loop" + attr.getLoop().getId();
varNames.add(name);
return name;
}
private String getUniqueVarName(String name) {
String r = name;
int i = 2;
while (varNames.contains(r)) {
r = name + i;
i++;
}
varNames.add(r);
return r;
}
private String makeArgName(RegisterArg arg) {
if (fallback) {
return getFallbackName(arg);
}
String name = arg.getName();
String varName;
if (name != null) {
if ("this".equals(name)) {
return name;
}
varName = name;
} else {
varName = makeNameForType(arg.getType());
}
if (NameMapper.isReserved(varName)) {
return varName + "R";
}
return varName;
}
private String getFallbackName(RegisterArg arg) {
String name = arg.getName();
String base = "r" + arg.getRegNum();
if (name != null && !name.equals("this")) {
return base + "_" + name;
}
return base;
}
private static String makeNameForType(ArgType type) {
if (type.isPrimitive()) {
return makeNameForPrimitive(type);
} else if (type.isArray()) {
return makeNameForType(type.getArrayRootElement()) + "Arr";
} else {
return makeNameForObject(type);
}
}
private static String makeNameForPrimitive(ArgType type) {
return type.getPrimitiveType().getShortName().toLowerCase();
}
private static String makeNameForObject(ArgType type) {
if (type.isObject()) {
String alias = getAliasForObject(type.getObject());
if (alias != null) {
return alias;
}
ClassInfo clsInfo = ClassInfo.fromType(type);
String shortName = clsInfo.getShortName();
String vName = fromName(shortName);
if (vName != null) {
return vName;
}
}
return Utils.escape(type.toString());
}
private static String fromName(String name) {
if (name == null || name.isEmpty()) {
return null;
}
if (name.toUpperCase().equals(name)) {
// all characters are upper case
return name.toLowerCase();
}
String v1 = Character.toLowerCase(name.charAt(0)) + name.substring(1);
if (!v1.equals(name)) {
return v1;
}
if (name.length() < 3) {
return name + "Var";
}
return null;
}
public static void guessName(RegisterArg arg) {
SSAVar sVar = arg.getSVar();
if (sVar == null || sVar.getName() != null) {
return;
}
RegisterArg assignArg = sVar.getAssign();
InsnNode assignInsn = assignArg.getParentInsn();
String name = makeNameFromInsn(assignInsn);
if (name != null && !NameMapper.isReserved(name)) {
assignArg.setName(name);
}
}
private static String getAliasForObject(String name) {
return OBJ_ALIAS.get(name);
}
private static String makeNameFromInsn(InsnNode insn) {
switch (insn.getType()) {
case INVOKE:
InvokeNode inv = (InvokeNode) insn;
String name = inv.getCallMth().getName();
if (name.startsWith("get") || name.startsWith("set")) {
return fromName(name.substring(3));
}
if ("iterator".equals(name)) {
return "it";
}
return name;
case CONSTRUCTOR:
ConstructorInsn co = (ConstructorInsn) insn;
return makeNameForObject(co.getClassType().getType());
case ARRAY_LENGTH:
return "length";
case ARITH:
case TERNARY:
case CAST:
for (InsnArg arg : insn.getArguments()) {
if (arg.isInsnWrap()) {
InsnNode wrapInsn = ((InsnWrapArg) arg).getWrapInsn();
String wName = makeNameFromInsn(wrapInsn);
if (wName != null) {
return wName;
}
}
}
break;
default:
break;
}
return null;
}
}
@@ -1,31 +1,38 @@
package jadx.core.codegen;
import jadx.core.dex.attributes.AttributeFlag;
import jadx.core.dex.attributes.AttributeType;
import jadx.core.dex.attributes.DeclareVariablesAttr;
import jadx.core.dex.attributes.ForceReturnAttr;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.DeclareVariablesAttr;
import jadx.core.dex.attributes.nodes.ForceReturnAttr;
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.instructions.IndexInsnNode;
import jadx.core.dex.instructions.SwitchNode;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.NamedArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.IBlock;
import jadx.core.dex.nodes.IContainer;
import jadx.core.dex.nodes.IRegion;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.regions.IfCondition;
import jadx.core.dex.regions.IfRegion;
import jadx.core.dex.regions.LoopRegion;
import jadx.core.dex.regions.Region;
import jadx.core.dex.regions.SwitchRegion;
import jadx.core.dex.regions.SynchronizedRegion;
import jadx.core.dex.trycatch.CatchAttr;
import jadx.core.dex.regions.TryCatchRegion;
import jadx.core.dex.regions.conditions.IfCondition;
import jadx.core.dex.regions.conditions.IfRegion;
import jadx.core.dex.regions.loops.ForEachLoop;
import jadx.core.dex.regions.loops.ForLoop;
import jadx.core.dex.regions.loops.LoopRegion;
import jadx.core.dex.regions.loops.LoopType;
import jadx.core.dex.trycatch.ExceptionHandler;
import jadx.core.dex.trycatch.TryCatchBlock;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.RegionUtils;
import jadx.core.utils.exceptions.CodegenException;
import jadx.core.utils.exceptions.JadxRuntimeException;
import java.util.List;
@@ -43,17 +50,21 @@ public class RegionGen extends InsnGen {
if (cont instanceof IBlock) {
makeSimpleBlock((IBlock) cont, code);
} else if (cont instanceof IRegion) {
declareVars(code, cont);
if (cont instanceof Region) {
makeSimpleRegion(code, (Region) cont);
} else if (cont instanceof IfRegion) {
makeIf((IfRegion) cont, code, true);
} else if (cont instanceof SwitchRegion) {
makeSwitch((SwitchRegion) cont, code);
} else if (cont instanceof LoopRegion) {
makeLoop((LoopRegion) cont, code);
} else if (cont instanceof SynchronizedRegion) {
makeSynchronizedRegion((SynchronizedRegion) cont, code);
} else {
declareVars(code, cont);
if (cont instanceof IfRegion) {
makeIf((IfRegion) cont, code, true);
} else if (cont instanceof SwitchRegion) {
makeSwitch((SwitchRegion) cont, code);
} else if (cont instanceof LoopRegion) {
makeLoop((LoopRegion) cont, code);
} else if (cont instanceof TryCatchRegion) {
makeTryCatch((TryCatchRegion) cont, code);
} else if (cont instanceof SynchronizedRegion) {
makeSynchronizedRegion((SynchronizedRegion) cont, code);
}
}
} else {
throw new CodegenException("Not processed container: " + cont);
@@ -61,8 +72,7 @@ public class RegionGen extends InsnGen {
}
private void declareVars(CodeWriter code, IContainer cont) {
DeclareVariablesAttr declVars =
(DeclareVariablesAttr) cont.getAttributes().get(AttributeType.DECLARE_VARIABLES);
DeclareVariablesAttr declVars = cont.get(AType.DECLARE_VARIABLES);
if (declVars != null) {
for (RegisterArg v : declVars.getVars()) {
code.startLine();
@@ -73,13 +83,9 @@ public class RegionGen extends InsnGen {
}
private void makeSimpleRegion(CodeWriter code, Region region) throws CodegenException {
CatchAttr tc = (CatchAttr) region.getAttributes().get(AttributeType.CATCH_BLOCK);
if (tc != null) {
makeTryCatch(region, tc.getTryBlock(), code);
} else {
for (IContainer c : region.getSubBlocks()) {
makeRegion(code, c);
}
declareVars(code, region);
for (IContainer c : region.getSubBlocks()) {
makeRegion(code, c);
}
}
@@ -91,22 +97,21 @@ public class RegionGen extends InsnGen {
private void makeSimpleBlock(IBlock block, CodeWriter code) throws CodegenException {
for (InsnNode insn : block.getInstructions()) {
makeInsn(insn, code);
if (!insn.contains(AFlag.SKIP)) {
makeInsn(insn, code);
}
}
IAttribute attr;
if ((attr = block.getAttributes().get(AttributeType.FORCE_RETURN)) != null) {
ForceReturnAttr retAttr = (ForceReturnAttr) attr;
ForceReturnAttr retAttr = block.get(AType.FORCE_RETURN);
if (retAttr != null) {
makeInsn(retAttr.getReturnInsn(), code);
}
}
private void makeIf(IfRegion region, CodeWriter code, boolean newLine) throws CodegenException {
if (region.getTernRegion() != null) {
makeSimpleBlock(region.getTernRegion().getBlock(), code);
return;
}
if (newLine) {
code.startLine();
code.startLineWithNum(region.getSourceLine());
} else {
code.attachSourceLine(region.getSourceLine());
}
code.add("if (");
new ConditionGen(this).add(code, region.getCondition());
@@ -130,16 +135,17 @@ public class RegionGen extends InsnGen {
* Connect if-else-if block
*/
private boolean connectElseIf(CodeWriter code, IContainer els) throws CodegenException {
if (els instanceof Region) {
Region re = (Region) els;
List<IContainer> subBlocks = re.getSubBlocks();
if (subBlocks.size() == 1 && subBlocks.get(0) instanceof IfRegion) {
IfRegion ifRegion = (IfRegion) subBlocks.get(0);
if (ifRegion.getAttributes().contains(AttributeFlag.ELSE_IF_CHAIN)) {
makeIf(ifRegion, code, false);
return true;
}
}
if (!els.contains(AFlag.ELSE_IF_CHAIN)) {
return false;
}
if (!(els instanceof Region)) {
return false;
}
List<IContainer> subBlocks = ((Region) els).getSubBlocks();
if (subBlocks.size() == 1
&& subBlocks.get(0) instanceof IfRegion) {
makeIf((IfRegion) subBlocks.get(0), code, false);
return true;
}
return false;
}
@@ -149,8 +155,7 @@ public class RegionGen extends InsnGen {
if (header != null) {
List<InsnNode> headerInsns = header.getInstructions();
if (headerInsns.size() > 1) {
// write not inlined instructions from header
mth.getAttributes().add(AttributeFlag.INCONSISTENT_CODE);
ErrorsCounter.methodError(mth, "Found not inlined instructions from loop header");
int last = headerInsns.size() - 1;
for (int i = 0; i < last; i++) {
InsnNode insn = headerInsns.get(i);
@@ -158,6 +163,10 @@ public class RegionGen extends InsnGen {
}
}
}
LoopLabelAttr labelAttr = region.getInfo().getStart().get(AType.LOOP_LABEL);
if (labelAttr != null) {
code.startLine(mgen.getNameGen().getLoopLabel(labelAttr)).add(':');
}
IfCondition condition = region.getCondition();
if (condition == null) {
@@ -167,8 +176,35 @@ public class RegionGen extends InsnGen {
code.startLine('}');
return code;
}
ConditionGen conditionGen = new ConditionGen(this);
LoopType type = region.getType();
if (type != null) {
if (type instanceof ForLoop) {
ForLoop forLoop = (ForLoop) type;
code.startLine("for (");
makeInsn(forLoop.getInitInsn(), code, Flags.INLINE);
code.add("; ");
conditionGen.add(code, condition);
code.add("; ");
makeInsn(forLoop.getIncrInsn(), code, Flags.INLINE);
code.add(") {");
makeRegionIndent(code, region.getBody());
code.startLine('}');
return code;
}
if (type instanceof ForEachLoop) {
ForEachLoop forEachLoop = (ForEachLoop) type;
code.startLine("for (");
declareVar(code, forEachLoop.getVarArg());
code.add(" : ");
addArg(code, forEachLoop.getIterableArg(), false);
code.add(") {");
makeRegionIndent(code, region.getBody());
code.startLine('}');
return code;
}
throw new JadxRuntimeException("Unknown loop type: " + type.getClass());
}
if (region.isConditionAtEnd()) {
code.startLine("do {");
makeRegionIndent(code, region.getBody());
@@ -197,7 +233,7 @@ public class RegionGen extends InsnGen {
SwitchNode insn = (SwitchNode) sw.getHeader().getInstructions().get(0);
InsnArg arg = insn.getArg(0);
code.startLine("switch (");
addArg(code, arg);
addArg(code, arg, false);
code.add(") {");
code.incIndent();
@@ -207,8 +243,15 @@ public class RegionGen extends InsnGen {
IContainer c = sw.getCases().get(i);
for (Object k : keys) {
code.startLine("case ");
if (k instanceof IndexInsnNode) {
code.add(staticField((FieldInfo) ((IndexInsnNode) k).getIndex()));
if (k instanceof FieldNode) {
FieldNode fn = (FieldNode) k;
if (fn.getParentClass().isEnum()) {
code.add(fn.getName());
} else {
staticField(code, fn.getFieldInfo());
}
} else if (k instanceof IndexInsnNode) {
staticField(code, (FieldInfo) ((IndexInsnNode) k).getIndex());
} else {
code.add(TypeGen.literalToString((Integer) k, arg.getType()));
}
@@ -238,18 +281,18 @@ public class RegionGen extends InsnGen {
}
}
private void makeTryCatch(IContainer region, TryCatchBlock tryCatchBlock, CodeWriter code)
throws CodegenException {
private void makeTryCatch(TryCatchRegion region, CodeWriter code) throws CodegenException {
TryCatchBlock tryCatchBlock = region.geTryCatchBlock();
code.startLine("try {");
region.getAttributes().remove(AttributeType.CATCH_BLOCK);
makeRegionIndent(code, region);
makeRegionIndent(code, region.getTryRegion());
// TODO: move search of 'allHandler' to 'TryCatchRegion'
ExceptionHandler allHandler = null;
for (ExceptionHandler handler : tryCatchBlock.getHandlers()) {
if (!handler.isCatchAll()) {
makeCatchBlock(code, handler);
} else {
if (allHandler != null) {
LOG.warn("Several 'all' handlers in try/catch block in " + mth);
LOG.warn("Several 'all' handlers in try/catch block in {}", mth);
}
allHandler = handler;
}
@@ -257,23 +300,32 @@ public class RegionGen extends InsnGen {
if (allHandler != null) {
makeCatchBlock(code, allHandler);
}
if (tryCatchBlock.getFinalBlock() != null) {
if (tryCatchBlock.getFinalRegion() != null) {
code.startLine("} finally {");
makeRegionIndent(code, tryCatchBlock.getFinalBlock());
makeRegionIndent(code, tryCatchBlock.getFinalRegion());
}
code.startLine('}');
}
private void makeCatchBlock(CodeWriter code, ExceptionHandler handler)
throws CodegenException {
private void makeCatchBlock(CodeWriter code, ExceptionHandler handler) throws CodegenException {
IContainer region = handler.getHandlerRegion();
if (region != null) {
code.startLine("} catch (");
code.add(handler.isCatchAll() ? "Throwable" : useClass(handler.getCatchType()));
code.add(' ');
code.add(mgen.assignNamedArg(handler.getArg()));
code.add(") {");
makeRegionIndent(code, region);
if (region == null) {
return;
}
code.startLine("} catch (");
InsnArg arg = handler.getArg();
if (arg instanceof RegisterArg) {
declareVar(code, (RegisterArg) arg);
} else if (arg instanceof NamedArg) {
if (handler.isCatchAll()) {
code.add("Throwable");
} else {
useClass(code, handler.getCatchType());
}
code.add(' ');
code.add(mgen.getNameGen().assignNamedArg((NamedArg) arg));
}
code.add(") {");
makeRegionIndent(code, region);
}
}
@@ -6,24 +6,17 @@ import jadx.core.utils.StringUtils;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxRuntimeException;
public class TypeGen {
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public static String translate(ClassGen clsGen, ArgType type) {
final PrimitiveType stype = type.getPrimitiveType();
if (stype == null) {
return type.toString();
}
if (stype == PrimitiveType.OBJECT) {
return clsGen.useClass(type);
}
if (stype == PrimitiveType.ARRAY) {
return translate(clsGen, type.getArrayElement()) + "[]";
}
return stype.getLongName();
public class TypeGen {
private static final Logger LOG = LoggerFactory.getLogger(TypeGen.class);
private TypeGen() {
}
public static String signature(ArgType type) {
final PrimitiveType stype = type.getPrimitiveType();
PrimitiveType stype = type.getPrimitiveType();
if (stype == PrimitiveType.OBJECT) {
return Utils.makeQualifiedObjectName(type.getObject());
}
@@ -70,7 +63,8 @@ public class TypeGen {
case OBJECT:
case ARRAY:
if (lit != 0) {
throw new JadxRuntimeException("Wrong object literal: " + type + " = " + lit);
LOG.warn("Wrong object literal: " + lit + " for type: " + type);
return Long.toString(lit);
}
return "null";
@@ -80,37 +74,91 @@ public class TypeGen {
}
public static String formatShort(short s) {
return "(short) " + wrapNegNum(s < 0, Short.toString(s));
if (s == Short.MAX_VALUE) {
return "Short.MAX_VALUE";
}
if (s == Short.MIN_VALUE) {
return "Short.MIN_VALUE";
}
return "(short) " + Short.toString(s);
}
public static String formatByte(byte b) {
return "(byte) " + wrapNegNum(b < 0, Byte.toString(b));
if (b == Byte.MAX_VALUE) {
return "Byte.MAX_VALUE";
}
if (b == Byte.MIN_VALUE) {
return "Byte.MIN_VALUE";
}
return "(byte) " + Byte.toString(b);
}
public static String formatInteger(int i) {
return wrapNegNum(i < 0, Integer.toString(i));
if (i == Integer.MAX_VALUE) {
return "Integer.MAX_VALUE";
}
if (i == Integer.MIN_VALUE) {
return "Integer.MIN_VALUE";
}
return Integer.toString(i);
}
public static String formatLong(long l) {
if (l == Long.MAX_VALUE) {
return "Long.MAX_VALUE";
}
if (l == Long.MIN_VALUE) {
return "Long.MIN_VALUE";
}
String str = Long.toString(l);
if (Math.abs(l) >= Integer.MAX_VALUE) {
str += "L";
}
return str;
}
public static String formatDouble(double d) {
return wrapNegNum(d < 0, Double.toString(d) + "d");
if (Double.isNaN(d)) {
return "Double.NaN";
}
if (d == Double.NEGATIVE_INFINITY) {
return "Double.NEGATIVE_INFINITY";
}
if (d == Double.POSITIVE_INFINITY) {
return "Double.POSITIVE_INFINITY";
}
if (d == Double.MIN_VALUE) {
return "Double.MIN_VALUE";
}
if (d == Double.MAX_VALUE) {
return "Double.MAX_VALUE";
}
if (d == Double.MIN_NORMAL) {
return "Double.MIN_NORMAL";
}
return Double.toString(d) + "d";
}
public static String formatFloat(float f) {
return wrapNegNum(f < 0, Float.toString(f) + "f");
}
public static String formatLong(long lit) {
String l = Long.toString(lit);
if (lit == Long.MIN_VALUE || Math.abs(lit) >= Integer.MAX_VALUE) {
l += "L";
if (Float.isNaN(f)) {
return "Float.NaN";
}
return wrapNegNum(lit < 0, l);
if (f == Float.NEGATIVE_INFINITY) {
return "Float.NEGATIVE_INFINITY";
}
if (f == Float.POSITIVE_INFINITY) {
return "Float.POSITIVE_INFINITY";
}
if (f == Float.MIN_VALUE) {
return "Float.MIN_VALUE";
}
if (f == Float.MAX_VALUE) {
return "Float.MAX_VALUE";
}
if (f == Float.MIN_NORMAL) {
return "Float.MIN_NORMAL";
}
return Float.toString(f) + "f";
}
private static String wrapNegNum(boolean lz, String str) {
// if (lz)
// return "(" + str + ")";
// else
return str;
}
}
@@ -1,6 +1,6 @@
package jadx.core.dex.attributes;
public enum AttributeFlag {
public enum AFlag {
TRY_ENTER,
TRY_LEAVE,
@@ -15,6 +15,7 @@ public enum AttributeFlag {
DONT_WRAP,
DONT_SHRINK,
DONT_INLINE,
DONT_GENERATE,
SKIP,
@@ -23,5 +24,8 @@ public enum AttributeFlag {
ELSE_IF_CHAIN,
WRAPPED,
ARITH_ONEARG,
INCONSISTENT_CODE, // warning about incorrect decompilation
}
@@ -0,0 +1,54 @@
package jadx.core.dex.attributes;
import jadx.core.dex.attributes.annotations.AnnotationsList;
import jadx.core.dex.attributes.annotations.MethodParameters;
import jadx.core.dex.attributes.nodes.DeclareVariablesAttr;
import jadx.core.dex.attributes.nodes.EnumClassAttr;
import jadx.core.dex.attributes.nodes.EnumMapAttr;
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
import jadx.core.dex.attributes.nodes.ForceReturnAttr;
import jadx.core.dex.attributes.nodes.JadxErrorAttr;
import jadx.core.dex.attributes.nodes.JumpInfo;
import jadx.core.dex.attributes.nodes.LoopInfo;
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
import jadx.core.dex.attributes.nodes.MethodInlineAttr;
import jadx.core.dex.attributes.nodes.PhiListAttr;
import jadx.core.dex.attributes.nodes.SourceFileAttr;
import jadx.core.dex.nodes.parser.FieldValueAttr;
import jadx.core.dex.trycatch.CatchAttr;
import jadx.core.dex.trycatch.ExcHandlerAttr;
import jadx.core.dex.trycatch.SplitterBlockAttr;
/**
* Attribute types enumeration,
* uses generic type for omit cast after in 'AttributeStorage.get' method
*
* @param <T> attribute class implementation
*/
public class AType<T extends IAttribute> {
private AType() {
}
public static final int FIELDS_COUNT = 18;
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<ExcHandlerAttr> EXC_HANDLER = new AType<ExcHandlerAttr>();
public static final AType<CatchAttr> CATCH_BLOCK = new AType<CatchAttr>();
public static final AType<SplitterBlockAttr> SPLITTER_BLOCK = new AType<SplitterBlockAttr>();
public static final AType<ForceReturnAttr> FORCE_RETURN = new AType<ForceReturnAttr>();
public static final AType<FieldValueAttr> FIELD_VALUE = new AType<FieldValueAttr>();
public static final AType<FieldReplaceAttr> FIELD_REPLACE = new AType<FieldReplaceAttr>();
public static final AType<JadxErrorAttr> JADX_ERROR = new AType<JadxErrorAttr>();
public static final AType<MethodInlineAttr> METHOD_INLINE = new AType<MethodInlineAttr>();
public static final AType<EnumClassAttr> ENUM_CLASS = new AType<EnumClassAttr>();
public static final AType<EnumMapAttr> ENUM_MAP = new AType<EnumMapAttr>();
public static final AType<AnnotationsList> ANNOTATION_LIST = new AType<AnnotationsList>();
public static final AType<MethodParameters> ANNOTATION_MTH_PARAMETERS = new AType<MethodParameters>();
public static final AType<PhiListAttr> PHI_LIST = new AType<PhiListAttr>();
public static final AType<SourceFileAttr> SOURCE_FILE = new AType<SourceFileAttr>();
public static final AType<DeclareVariablesAttr> DECLARE_VARIABLES = new AType<DeclareVariablesAttr>();
public static final AType<LoopLabelAttr> LOOP_LABEL = new AType<LoopLabelAttr>();
}
@@ -0,0 +1,30 @@
package jadx.core.dex.attributes;
import jadx.core.utils.Utils;
import java.util.LinkedList;
import java.util.List;
public class AttrList<T> implements IAttribute {
private final AType<AttrList<T>> type;
private final List<T> list = new LinkedList<T>();
public AttrList(AType<AttrList<T>> type) {
this.type = type;
}
public List<T> getList() {
return list;
}
@Override
public AType<AttrList<T>> getType() {
return type;
}
@Override
public String toString() {
return Utils.listToString(list);
}
}
@@ -1,15 +1,96 @@
package jadx.core.dex.attributes;
import jadx.core.dex.attributes.annotations.Annotation;
import java.util.List;
public abstract class AttrNode implements IAttributeNode {
private AttributesList attributesList;
private static final AttributeStorage EMPTY_ATTR_STORAGE = new EmptyAttrStorage();
private AttributeStorage storage = EMPTY_ATTR_STORAGE;
@Override
public AttributesList getAttributes() {
if (attributesList == null) {
attributesList = new AttributesList();
}
return attributesList;
public void add(AFlag flag) {
initStorage().add(flag);
}
@Override
public void addAttr(IAttribute attr) {
initStorage().add(attr);
}
@Override
public <T> void addAttr(AType<AttrList<T>> type, T obj) {
initStorage().add(type, obj);
}
@Override
public void copyAttributesFrom(AttrNode attrNode) {
initStorage().addAll(attrNode.storage);
}
AttributeStorage initStorage() {
AttributeStorage store = storage;
if (store == EMPTY_ATTR_STORAGE) {
store = new AttributeStorage();
storage = store;
}
return store;
}
@Override
public boolean contains(AFlag flag) {
return storage.contains(flag);
}
@Override
public <T extends IAttribute> boolean contains(AType<T> type) {
return storage.contains(type);
}
@Override
public <T extends IAttribute> T get(AType<T> type) {
return storage.get(type);
}
@Override
public Annotation getAnnotation(String cls) {
return storage.getAnnotation(cls);
}
@Override
public <T> List<T> getAll(AType<AttrList<T>> type) {
return storage.getAll(type);
}
@Override
public void remove(AFlag flag) {
storage.remove(flag);
}
@Override
public <T extends IAttribute> void remove(AType<T> type) {
storage.remove(type);
}
@Override
public void removeAttr(IAttribute attr) {
storage.remove(attr);
}
@Override
public void clearAttributes() {
storage.clear();
}
@Override
public List<String> getAttributesStringsList() {
return storage.getAttributeStrings();
}
@Override
public String getAttributesString() {
return storage.toString();
}
}
@@ -0,0 +1,122 @@
package jadx.core.dex.attributes;
import jadx.core.dex.attributes.annotations.Annotation;
import jadx.core.dex.attributes.annotations.AnnotationsList;
import jadx.core.utils.Utils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Storage for different attribute types:
* 1. flags - boolean attribute (set or not)
* 2. attribute - class instance associated with attribute type.
*/
public class AttributeStorage {
private final Set<AFlag> flags;
private final Map<AType<?>, IAttribute> attributes;
public AttributeStorage() {
flags = EnumSet.noneOf(AFlag.class);
attributes = new IdentityHashMap<AType<?>, IAttribute>(AType.FIELDS_COUNT);
}
public void add(AFlag flag) {
flags.add(flag);
}
public void add(IAttribute attr) {
attributes.put(attr.getType(), attr);
}
public <T> void add(AType<AttrList<T>> type, T obj) {
AttrList<T> list = get(type);
if (list == null) {
list = new AttrList<T>(type);
add(list);
}
list.getList().add(obj);
}
public void addAll(AttributeStorage otherList) {
flags.addAll(otherList.flags);
attributes.putAll(otherList.attributes);
}
public boolean contains(AFlag flag) {
return flags.contains(flag);
}
public <T extends IAttribute> boolean contains(AType<T> type) {
return attributes.containsKey(type);
}
@SuppressWarnings("unchecked")
public <T extends IAttribute> T get(AType<T> type) {
return (T) attributes.get(type);
}
public Annotation getAnnotation(String cls) {
AnnotationsList aList = get(AType.ANNOTATION_LIST);
return aList == null ? null : aList.get(cls);
}
public <T> List<T> getAll(AType<AttrList<T>> type) {
AttrList<T> attrList = get(type);
if (attrList == null) {
return Collections.emptyList();
}
return Collections.unmodifiableList(attrList.getList());
}
public void remove(AFlag flag) {
flags.remove(flag);
}
public <T extends IAttribute> void remove(AType<T> type) {
attributes.remove(type);
}
public void remove(IAttribute attr) {
AType<?> type = attr.getType();
IAttribute a = attributes.get(type);
if (a == attr) {
attributes.remove(type);
}
}
public void clear() {
flags.clear();
attributes.clear();
}
public List<String> getAttributeStrings() {
int size = flags.size() + attributes.size() + attributes.size();
if (size == 0) {
return Collections.emptyList();
}
List<String> list = new ArrayList<String>(size);
for (AFlag a : flags) {
list.add(a.toString());
}
for (IAttribute a : attributes.values()) {
list.add(a.toString());
}
return list;
}
@Override
public String toString() {
List<String> list = getAttributeStrings();
if (list.isEmpty()) {
return "";
}
return "A:{" + Utils.listToString(list) + "}";
}
}
@@ -1,65 +0,0 @@
package jadx.core.dex.attributes;
public enum AttributeType {
/* Multi attributes */
JUMP(false),
LOOP(false),
CATCH_BLOCK(false),
/* Uniq attributes */
EXC_HANDLER(true),
SPLITTER_BLOCK(true),
FORCE_RETURN(true),
FIELD_VALUE(true),
JADX_ERROR(true),
METHOD_INLINE(true),
FIELD_REPLACE(true),
ENUM_CLASS(true),
ANNOTATION_LIST(true),
ANNOTATION_MTH_PARAMETERS(true),
SOURCE_FILE(true),
// for regions
DECLARE_VARIABLES(true);
private static final int NOT_UNIQ_COUNT;
private final boolean uniq;
private AttributeType(boolean isUniq) {
this.uniq = isUniq;
}
static {
// place all not unique attributes at first
int last = -1;
AttributeType[] vals = AttributeType.values();
for (int i = 0; i < vals.length; i++) {
AttributeType type = vals[i];
if (type.notUniq()) {
last = i;
}
}
NOT_UNIQ_COUNT = last + 1;
}
public static int getNotUniqCount() {
return NOT_UNIQ_COUNT;
}
public boolean isUniq() {
return uniq;
}
public boolean notUniq() {
return !uniq;
}
}
@@ -1,202 +0,0 @@
package jadx.core.dex.attributes;
import jadx.core.dex.attributes.annotations.Annotation;
import jadx.core.dex.attributes.annotations.AnnotationsList;
import jadx.core.utils.Utils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Storage for different attribute types:
* 1. flags - boolean attribute (set or not)
* 2. attribute - class instance associated for attribute type,
* only one attached to node for unique attributes, multiple for others
*/
public final class AttributesList {
private final Set<AttributeFlag> flags;
private final Map<AttributeType, IAttribute> uniqAttr;
private final List<IAttribute> attributes;
private final int[] attrCount;
public AttributesList() {
flags = EnumSet.noneOf(AttributeFlag.class);
uniqAttr = new EnumMap<AttributeType, IAttribute>(AttributeType.class);
attributes = new LinkedList<IAttribute>();
attrCount = new int[AttributeType.getNotUniqCount()];
}
// Flags
public void add(AttributeFlag flag) {
flags.add(flag);
}
public boolean contains(AttributeFlag flag) {
return flags.contains(flag);
}
public void remove(AttributeFlag flag) {
flags.remove(flag);
}
// Attributes
public void add(IAttribute attr) {
if (attr.getType().isUniq()) {
uniqAttr.put(attr.getType(), attr);
} else {
addMultiAttribute(attr);
}
}
private void addMultiAttribute(IAttribute attr) {
attributes.add(attr);
attrCount[attr.getType().ordinal()]++;
}
private int getMultiCountInternal(AttributeType type) {
return attrCount[type.ordinal()];
}
public void addAll(AttributesList otherList) {
flags.addAll(otherList.flags);
uniqAttr.putAll(otherList.uniqAttr);
for (IAttribute attr : otherList.attributes) {
addMultiAttribute(attr);
}
}
public boolean contains(AttributeType type) {
if (type.isUniq()) {
return uniqAttr.containsKey(type);
} else {
return getMultiCountInternal(type) != 0;
}
}
public IAttribute get(AttributeType type) {
if (type.isUniq()) {
return uniqAttr.get(type);
} else {
if (getMultiCountInternal(type) != 0) {
for (IAttribute attr : attributes) {
if (attr.getType() == type) {
return attr;
}
}
}
return null;
}
}
public int getCount(AttributeType type) {
if (type.isUniq()) {
return uniqAttr.containsKey(type) ? 1 : 0;
} else {
return getMultiCountInternal(type);
}
}
public Annotation getAnnotation(String cls) {
AnnotationsList aList = (AnnotationsList) get(AttributeType.ANNOTATION_LIST);
return aList == null ? null : aList.get(cls);
}
public List<IAttribute> getAll(AttributeType type) {
assert type.notUniq();
int count = getMultiCountInternal(type);
if (count == 0) {
return Collections.emptyList();
} else {
List<IAttribute> attrs = new ArrayList<IAttribute>(count);
for (IAttribute attr : attributes) {
if (attr.getType() == type) {
attrs.add(attr);
}
}
return attrs;
}
}
public void remove(AttributeType type) {
if (type.isUniq()) {
uniqAttr.remove(type);
} else {
for (Iterator<IAttribute> it = attributes.iterator(); it.hasNext(); ) {
IAttribute attr = it.next();
if (attr.getType() == type) {
it.remove();
}
}
attrCount[type.ordinal()] = 0;
}
}
public void remove(IAttribute attr) {
AttributeType type = attr.getType();
if (type.isUniq()) {
IAttribute a = uniqAttr.get(type);
if (a == attr) {
uniqAttr.remove(type);
}
} else {
if (getMultiCountInternal(type) == 0) {
return;
}
for (Iterator<IAttribute> it = attributes.iterator(); it.hasNext(); ) {
IAttribute a = it.next();
if (a == attr) {
it.remove();
attrCount[type.ordinal()]--;
}
}
}
}
public void clear() {
flags.clear();
uniqAttr.clear();
attributes.clear();
Arrays.fill(attrCount, 0);
}
public List<String> getAttributeStrings() {
int size = flags.size() + uniqAttr.size() + attributes.size();
if (size == 0) {
return Collections.emptyList();
}
List<String> list = new ArrayList<String>(size);
for (AttributeFlag a : flags) {
list.add(a.toString());
}
for (IAttribute a : uniqAttr.values()) {
list.add(a.toString());
}
for (IAttribute a : attributes) {
list.add(a.toString());
}
return list;
}
@Override
public String toString() {
List<String> list = getAttributeStrings();
if (list.isEmpty()) {
return "";
}
return "A:{" + Utils.listToString(list) + "}";
}
}
@@ -1,56 +0,0 @@
package jadx.core.dex.attributes;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.args.TypedVar;
import jadx.core.dex.nodes.MethodNode;
public final class BlockRegState {
private final RegisterArg[] regs;
public BlockRegState(MethodNode mth) {
this.regs = new RegisterArg[mth.getRegsCount()];
for (int i = 0; i < regs.length; i++) {
regs[i] = new RegisterArg(i);
}
}
public BlockRegState(BlockRegState state) {
this.regs = new RegisterArg[state.regs.length];
System.arraycopy(state.regs, 0, regs, 0, state.regs.length);
}
public void assignReg(RegisterArg arg) {
regs[arg.getRegNum()] = arg;
arg.getTypedVar().getUseList().add(arg);
}
public void use(RegisterArg arg) {
RegisterArg reg = regs[arg.getRegNum()];
TypedVar regType = reg.getTypedVar();
if (regType == null) {
regType = new TypedVar(arg.getType());
reg.forceSetTypedVar(regType);
}
arg.replaceTypedVar(reg);
reg.getTypedVar().getUseList().add(arg);
}
public RegisterArg getRegister(int r) {
return regs[r];
}
@Override
public String toString() {
StringBuilder str = new StringBuilder();
for (RegisterArg reg : regs) {
if (reg.getTypedVar() != null) {
if (str.length() != 0) {
str.append(", ");
}
str.append(reg);
}
}
return str.toString();
}
}
@@ -0,0 +1,60 @@
package jadx.core.dex.attributes;
import jadx.core.dex.attributes.annotations.Annotation;
import java.util.Collections;
import java.util.List;
public final class EmptyAttrStorage extends AttributeStorage {
@Override
public boolean contains(AFlag flag) {
return false;
}
@Override
public <T extends IAttribute> boolean contains(AType<T> type) {
return false;
}
@Override
public <T extends IAttribute> T get(AType<T> type) {
return null;
}
@Override
public Annotation getAnnotation(String cls) {
return null;
}
@Override
public <T> List<T> getAll(AType<AttrList<T>> type) {
return Collections.emptyList();
}
@Override
public void clear() {
}
@Override
public void remove(AFlag flag) {
}
@Override
public <T extends IAttribute> void remove(AType<T> type) {
}
@Override
public void remove(IAttribute attr) {
}
@Override
public List<String> getAttributeStrings() {
return Collections.emptyList();
}
@Override
public String toString() {
return "";
}
}
@@ -2,6 +2,6 @@ package jadx.core.dex.attributes;
public interface IAttribute {
AttributeType getType();
AType<?> getType();
}
@@ -1,7 +1,38 @@
package jadx.core.dex.attributes;
import jadx.core.dex.attributes.annotations.Annotation;
import java.util.List;
public interface IAttributeNode {
AttributesList getAttributes();
void add(AFlag flag);
void addAttr(IAttribute attr);
<T> void addAttr(AType<AttrList<T>> type, T obj);
void copyAttributesFrom(AttrNode attrNode);
boolean contains(AFlag flag);
<T extends IAttribute> boolean contains(AType<T> type);
<T extends IAttribute> T get(AType<T> type);
Annotation getAnnotation(String cls);
<T> List<T> getAll(AType<AttrList<T>> type);
void remove(AFlag flag);
<T extends IAttribute> void remove(AType<T> type);
void removeAttr(IAttribute attr);
void clearAttributes();
List<String> getAttributesStringsList();
String getAttributesString();
}
@@ -6,7 +6,7 @@ import java.util.Map;
public class Annotation {
public static enum Visibility {
public enum Visibility {
BUILD, RUNTIME, SYSTEM
}
@@ -1,6 +1,6 @@
package jadx.core.dex.attributes.annotations;
import jadx.core.dex.attributes.AttributeType;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.utils.Utils;
@@ -32,9 +32,13 @@ public class AnnotationsList implements IAttribute {
return map.size();
}
public boolean isEmpty() {
return map.isEmpty();
}
@Override
public AttributeType getType() {
return AttributeType.ANNOTATION_LIST;
public AType<AnnotationsList> getType() {
return AType.ANNOTATION_LIST;
}
@Override
@@ -1,6 +1,6 @@
package jadx.core.dex.attributes.annotations;
import jadx.core.dex.attributes.AttributeType;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.utils.Utils;
@@ -20,8 +20,8 @@ public class MethodParameters implements IAttribute {
}
@Override
public AttributeType getType() {
return AttributeType.ANNOTATION_MTH_PARAMETERS;
public AType<MethodParameters> getType() {
return AType.ANNOTATION_MTH_PARAMETERS;
}
@Override
@@ -1,5 +1,7 @@
package jadx.core.dex.attributes;
package jadx.core.dex.attributes.nodes;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.utils.Utils;
@@ -22,8 +24,8 @@ public class DeclareVariablesAttr implements IAttribute {
}
@Override
public AttributeType getType() {
return AttributeType.DECLARE_VARIABLES;
public AType<DeclareVariablesAttr> getType() {
return AType.DECLARE_VARIABLES;
}
@Override
@@ -1,5 +1,7 @@
package jadx.core.dex.attributes;
package jadx.core.dex.attributes.nodes;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.MethodNode;
@@ -51,7 +53,7 @@ public class EnumClassAttr implements IAttribute {
private MethodNode staticMethod;
public EnumClassAttr(int fieldsCount) {
this.fields = new ArrayList<EnumClassAttr.EnumField>(fieldsCount);
this.fields = new ArrayList<EnumField>(fieldsCount);
}
public List<EnumField> getFields() {
@@ -67,8 +69,8 @@ public class EnumClassAttr implements IAttribute {
}
@Override
public AttributeType getType() {
return AttributeType.ENUM_CLASS;
public AType<EnumClassAttr> getType() {
return AType.ENUM_CLASS;
}
@Override
@@ -0,0 +1,49 @@
package jadx.core.dex.attributes.nodes;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.nodes.FieldNode;
import java.util.HashMap;
import java.util.Map;
public class EnumMapAttr implements IAttribute {
public static class KeyValueMap {
private Map<Object, Object> map = new HashMap<Object, Object>();
public Object get(Object key) {
return map.get(key);
}
void put(Object key, Object value) {
map.put(key, value);
}
}
private Map<FieldNode, KeyValueMap> fieldsMap = new HashMap<FieldNode, KeyValueMap>();
public KeyValueMap getMap(FieldNode field) {
return fieldsMap.get(field);
}
public void add(FieldNode field, Object key, Object value) {
KeyValueMap map = getMap(field);
if (map == null) {
map = new KeyValueMap();
fieldsMap.put(field, map);
}
map.put(key, value);
}
@Override
public AType<EnumMapAttr> getType() {
return AType.ENUM_MAP;
}
@Override
public String toString() {
return "Enum fields map: " + fieldsMap;
}
}
@@ -1,5 +1,7 @@
package jadx.core.dex.attributes;
package jadx.core.dex.attributes.nodes;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.info.FieldInfo;
public class FieldReplaceAttr implements IAttribute {
@@ -21,8 +23,8 @@ public class FieldReplaceAttr implements IAttribute {
}
@Override
public AttributeType getType() {
return AttributeType.FIELD_REPLACE;
public AType<FieldReplaceAttr> getType() {
return AType.FIELD_REPLACE;
}
@Override
@@ -1,5 +1,7 @@
package jadx.core.dex.attributes;
package jadx.core.dex.attributes.nodes;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.utils.Utils;
@@ -16,8 +18,8 @@ public class ForceReturnAttr implements IAttribute {
}
@Override
public AttributeType getType() {
return AttributeType.FORCE_RETURN;
public AType<ForceReturnAttr> getType() {
return AType.FORCE_RETURN;
}
@Override
@@ -1,5 +1,7 @@
package jadx.core.dex.attributes;
package jadx.core.dex.attributes.nodes;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.utils.Utils;
public class JadxErrorAttr implements IAttribute {
@@ -15,8 +17,8 @@ public class JadxErrorAttr implements IAttribute {
}
@Override
public AttributeType getType() {
return AttributeType.JADX_ERROR;
public AType<JadxErrorAttr> getType() {
return AType.JADX_ERROR;
}
@Override
@@ -1,22 +1,17 @@
package jadx.core.dex.attributes;
package jadx.core.dex.attributes.nodes;
import jadx.core.utils.InsnUtils;
public class JumpAttribute implements IAttribute {
public class JumpInfo {
private final int src;
private final int dest;
public JumpAttribute(int src, int dest) {
public JumpInfo(int src, int dest) {
this.src = src;
this.dest = dest;
}
@Override
public AttributeType getType() {
return AttributeType.JUMP;
}
public int getSrc() {
return src;
}
@@ -25,11 +20,6 @@ public class JumpAttribute implements IAttribute {
return dest;
}
@Override
public String toString() {
return "JUMP: " + InsnUtils.formatOffset(src) + " -> " + InsnUtils.formatOffset(dest);
}
@Override
public int hashCode() {
return 31 * dest + src;
@@ -46,7 +36,12 @@ public class JumpAttribute implements IAttribute {
if (getClass() != obj.getClass()) {
return false;
}
JumpAttribute other = (JumpAttribute) obj;
JumpInfo other = (JumpInfo) obj;
return dest == other.dest && src == other.src;
}
@Override
public String toString() {
return "JUMP: " + InsnUtils.formatOffset(src) + " -> " + InsnUtils.formatOffset(dest);
}
}
@@ -1,4 +1,6 @@
package jadx.core.dex.attributes;
package jadx.core.dex.attributes.nodes;
import jadx.core.dex.attributes.AttrNode;
public abstract class LineAttrNode extends AttrNode {
@@ -1,5 +1,6 @@
package jadx.core.dex.attributes;
package jadx.core.dex.attributes.nodes;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.Edge;
import jadx.core.utils.BlockUtils;
@@ -10,13 +11,16 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Set;
public class LoopAttr implements IAttribute {
public class LoopInfo {
private final BlockNode start;
private final BlockNode end;
private final Set<BlockNode> loopBlocks;
public LoopAttr(BlockNode start, BlockNode end) {
private int id;
private LoopInfo parentLoop;
public LoopInfo(BlockNode start, BlockNode end) {
this.start = start;
this.end = end;
this.loopBlocks = Collections.unmodifiableSet(BlockUtils.getAllPathsBlocks(start, end));
@@ -30,11 +34,6 @@ public class LoopAttr implements IAttribute {
return end;
}
@Override
public AttributeType getType() {
return AttributeType.LOOP;
}
public Set<BlockNode> getLoopBlocks() {
return loopBlocks;
}
@@ -49,7 +48,7 @@ public class LoopAttr implements IAttribute {
for (BlockNode block : blocks) {
// exit: successor node not from this loop, (don't change to getCleanSuccessors)
for (BlockNode s : block.getSuccessors()) {
if (!blocks.contains(s) && !s.getAttributes().contains(AttributeType.EXC_HANDLER)) {
if (!blocks.contains(s) && !s.contains(AType.EXC_HANDLER)) {
nodes.add(block);
}
}
@@ -65,7 +64,7 @@ public class LoopAttr implements IAttribute {
Set<BlockNode> blocks = getLoopBlocks();
for (BlockNode block : blocks) {
for (BlockNode s : block.getSuccessors()) {
if (!blocks.contains(s) && !s.getAttributes().contains(AttributeType.EXC_HANDLER)) {
if (!blocks.contains(s) && !s.contains(AType.EXC_HANDLER)) {
edges.add(new Edge(block, s));
}
}
@@ -73,8 +72,24 @@ public class LoopAttr implements IAttribute {
return edges;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public LoopInfo getParentLoop() {
return parentLoop;
}
public void setParentLoop(LoopInfo parentLoop) {
this.parentLoop = parentLoop;
}
@Override
public String toString() {
return "LOOP: " + start + "->" + end;
return "LOOP:" + id + ": " + start + "->" + end;
}
}
@@ -0,0 +1,27 @@
package jadx.core.dex.attributes.nodes;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
public class LoopLabelAttr implements IAttribute {
private final LoopInfo loop;
public LoopLabelAttr(LoopInfo loop) {
this.loop = loop;
}
public LoopInfo getLoop() {
return loop;
}
@Override
public AType<LoopLabelAttr> getType() {
return AType.LOOP_LABEL;
}
@Override
public String toString() {
return "LOOP_LABEL: " + loop;
}
}
@@ -1,5 +1,7 @@
package jadx.core.dex.attributes;
package jadx.core.dex.attributes.nodes;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.nodes.InsnNode;
public class MethodInlineAttr implements IAttribute {
@@ -15,8 +17,8 @@ public class MethodInlineAttr implements IAttribute {
}
@Override
public AttributeType getType() {
return AttributeType.METHOD_INLINE;
public AType<MethodInlineAttr> getType() {
return AType.METHOD_INLINE;
}
@Override
@@ -0,0 +1,32 @@
package jadx.core.dex.attributes.nodes;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.instructions.PhiInsn;
import java.util.LinkedList;
import java.util.List;
public class PhiListAttr implements IAttribute {
private final List<PhiInsn> list = new LinkedList<PhiInsn>();
@Override
public AType<PhiListAttr> getType() {
return AType.PHI_LIST;
}
public List<PhiInsn> getList() {
return list;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("PHI: ");
for (PhiInsn phiInsn : list) {
sb.append('r').append(phiInsn.getResult().getRegNum()).append(" ");
}
return sb.toString();
}
}
@@ -1,4 +1,7 @@
package jadx.core.dex.attributes;
package jadx.core.dex.attributes.nodes;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
public class SourceFileAttr implements IAttribute {
@@ -13,8 +16,8 @@ public class SourceFileAttr implements IAttribute {
}
@Override
public AttributeType getType() {
return AttributeType.SOURCE_FILE;
public AType<SourceFileAttr> getType() {
return AType.SOURCE_FILE;
}
@Override
@@ -8,7 +8,7 @@ public class AccessInfo {
private final int accFlags;
public static enum AFType {
public enum AFType {
CLASS, FIELD, METHOD
}
@@ -3,7 +3,7 @@ package jadx.core.dex.info;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.DexNode;
import com.android.dx.io.FieldId;
import com.android.dex.FieldId;
public class FieldInfo {
@@ -7,8 +7,8 @@ import jadx.core.utils.Utils;
import java.util.List;
import com.android.dx.io.MethodId;
import com.android.dx.io.ProtoId;
import com.android.dex.MethodId;
import com.android.dex.ProtoId;
public final class MethodInfo {
@@ -1,5 +1,6 @@
package jadx.core.dex.instructions;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.RegisterArg;
@@ -51,16 +52,31 @@ public class ArithNode extends InsnNode {
}
public ArithNode(ArithOp op, RegisterArg res, InsnArg a) {
super(InsnType.ARITH_ONEARG, 1);
this.op = op;
setResult(res);
addArg(a);
this(op, res, res, a);
add(AFlag.ARITH_ONEARG);
}
public ArithOp getOp() {
return op;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof ArithNode) || !super.equals(obj)) {
return false;
}
ArithNode that = (ArithNode) obj;
return op == that.op;
}
@Override
public int hashCode() {
return 31 * super.hashCode() + op.hashCode();
}
@Override
public String toString() {
return InsnUtils.formatOffset(offset) + ": "
@@ -68,7 +84,7 @@ public class ArithNode extends InsnNode {
+ getResult() + " = "
+ getArg(0) + " "
+ op.getSymbol() + " "
+ (getArgsCount() == 2 ? getArg(1) : "");
+ getArg(1);
}
}
@@ -17,7 +17,7 @@ public enum ArithOp {
private final String symbol;
private ArithOp(String symbol) {
ArithOp(String symbol) {
this.symbol = symbol;
}
@@ -3,7 +3,7 @@ package jadx.core.dex.instructions;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.InsnNode;
public class ConstClassNode extends InsnNode {
public final class ConstClassNode extends InsnNode {
private final ArgType clsType;
@@ -16,6 +16,23 @@ public class ConstClassNode extends InsnNode {
return clsType;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof ConstClassNode) || !super.equals(obj)) {
return false;
}
ConstClassNode that = (ConstClassNode) obj;
return clsType.equals(that.clsType);
}
@Override
public int hashCode() {
return 31 * super.hashCode() + clsType.hashCode();
}
@Override
public String toString() {
return super.toString() + " " + clsType;
@@ -2,7 +2,7 @@ package jadx.core.dex.instructions;
import jadx.core.dex.nodes.InsnNode;
public class ConstStringNode extends InsnNode {
public final class ConstStringNode extends InsnNode {
private final String str;
@@ -15,6 +15,23 @@ public class ConstStringNode extends InsnNode {
return str;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof ConstStringNode) || !super.equals(obj)) {
return false;
}
ConstStringNode that = (ConstStringNode) obj;
return str.equals(that.str);
}
@Override
public int hashCode() {
return 31 * super.hashCode() + str.hashCode();
}
@Override
public String toString() {
return super.toString() + " \"" + str + "\"";
@@ -8,7 +8,7 @@ import jadx.core.utils.exceptions.JadxRuntimeException;
import com.android.dx.io.instructions.FillArrayDataPayloadDecodedInstruction;
public class FillArrayNode extends InsnNode {
public final class FillArrayNode extends InsnNode {
private final Object data;
private ArgType elemType;
@@ -53,4 +53,21 @@ public class FillArrayNode extends InsnNode {
elemType = r;
}
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof FillArrayNode) || !super.equals(obj)) {
return false;
}
FillArrayNode that = (FillArrayNode) obj;
return elemType.equals(that.elemType) && data == that.data;
}
@Override
public int hashCode() {
return 31 * super.hashCode() + elemType.hashCode() + data.hashCode();
}
}
@@ -20,6 +20,23 @@ public class GotoNode extends InsnNode {
return target;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof GotoNode) || !super.equals(obj)) {
return false;
}
GotoNode gotoNode = (GotoNode) obj;
return target == gotoNode.target;
}
@Override
public int hashCode() {
return 31 * super.hashCode() + target;
}
@Override
public String toString() {
return super.toString() + "-> " + InsnUtils.formatOffset(target);
@@ -70,6 +70,23 @@ public class IfNode extends GotoNode {
return elseBlock;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof IfNode) || !super.equals(obj)) {
return false;
}
IfNode ifNode = (IfNode) obj;
return op == ifNode.op;
}
@Override
public int hashCode() {
return 31 * super.hashCode() + op.hashCode();
}
@Override
public String toString() {
return InsnUtils.formatOffset(offset) + ": "
@@ -12,7 +12,7 @@ public enum IfOp {
private final String symbol;
private IfOp(String symbol) {
IfOp(String symbol) {
this.symbol = symbol;
}
@@ -23,19 +23,19 @@ public enum IfOp {
public IfOp invert() {
switch (this) {
case EQ:
return IfOp.NE;
return NE;
case NE:
return IfOp.EQ;
return EQ;
case LT:
return IfOp.GE;
return GE;
case LE:
return IfOp.GT;
return GT;
case GT:
return IfOp.LE;
return LE;
case GE:
return IfOp.LT;
return LT;
default:
throw new JadxRuntimeException("Unknown if operations type: " + this);
@@ -16,6 +16,23 @@ public class IndexInsnNode extends InsnNode {
return index;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof IndexInsnNode) || !super.equals(obj)) {
return false;
}
IndexInsnNode that = (IndexInsnNode) obj;
return index == null ? that.index == null : index.equals(that.index);
}
@Override
public int hashCode() {
return 31 * super.hashCode() + (index != null ? index.hashCode() : 0);
}
@Override
public String toString() {
return super.toString() + " " + InsnUtils.indexToString(index);
@@ -4,37 +4,52 @@ import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.NamedArg;
import jadx.core.dex.instructions.args.PrimitiveType;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.DexNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.InsnUtils;
import jadx.core.utils.exceptions.DecodeException;
import com.android.dx.io.Code;
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 final MethodNode method;
private final DecodedInstruction[] insnArr;
private final DexNode dex;
private DecodedInstruction[] insnArr;
public InsnDecoder(MethodNode mthNode, Code mthCode) {
public InsnDecoder(MethodNode mthNode) {
this.method = mthNode;
this.dex = method.dex();
this.insnArr = DecodedInstruction.decodeAll(mthCode.getInstructions());
}
public InsnNode[] run() throws DecodeException {
InsnNode[] instructions = new InsnNode[insnArr.length];
public void decodeInsns(Code mthCode) throws DecodeException {
short[] encodedInstructions = mthCode.getInstructions();
int size = encodedInstructions.length;
DecodedInstruction[] decoded = new DecodedInstruction[size];
ShortArrayCodeInput in = new ShortArrayCodeInput(encodedInstructions);
try {
while (in.hasMore()) {
decoded[in.cursor()] = DecodedInstruction.decode(in);
}
} catch (Exception e) {
throw new DecodeException(method, "", e);
}
insnArr = decoded;
}
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) {
@@ -48,6 +63,7 @@ public class InsnDecoder {
instructions[i] = null;
}
}
insnArr = null;
return instructions;
}
@@ -388,8 +404,7 @@ public class InsnDecoder {
case Opcodes.MOVE_EXCEPTION:
return insn(InsnType.MOVE_EXCEPTION,
InsnArg.reg(insn, 0, ArgType.unknown(PrimitiveType.OBJECT)),
new NamedArg("e", ArgType.unknown(PrimitiveType.OBJECT)));
InsnArg.reg(insn, 0, ArgType.unknown(PrimitiveType.OBJECT)));
case Opcodes.RETURN_VOID:
return new InsnNode(InsnType.RETURN, 0);
@@ -606,16 +621,19 @@ public class InsnDecoder {
int resReg = getMoveResultRegister(insnArr, offset);
ArgType arrType = dex.getType(insn.getIndex());
ArgType elType = arrType.getArrayElement();
InsnArg[] regs = new InsnArg[insn.getRegisterCount()];
boolean typeImmutable = elType.isPrimitive();
int regsCount = insn.getRegisterCount();
InsnArg[] regs = new InsnArg[regsCount];
if (isRange) {
int r = insn.getA();
for (int i = 0; i < insn.getRegisterCount(); i++) {
regs[i] = InsnArg.reg(r, elType);
for (int i = 0; i < regsCount; i++) {
regs[i] = InsnArg.reg(r, elType, typeImmutable);
r++;
}
} else {
for (int i = 0; i < insn.getRegisterCount(); i++) {
regs[i] = InsnArg.reg(insn, i, elType);
for (int i = 0; i < regsCount; i++) {
int regNum = InsnUtils.getArg(insn, i);
regs[i] = InsnArg.reg(regNum, elType, typeImmutable);
}
}
return insn(InsnType.FILLED_NEW_ARRAY,
@@ -1,7 +1,6 @@
package jadx.core.dex.instructions;
public enum InsnType {
NOP, // replacement for removed instructions
CONST,
CONST_STR,
@@ -48,16 +47,24 @@ public enum InsnType {
INVOKE,
// additional instructions
// *** Additional instructions ***
// replacement for removed instructions
NOP,
TERNARY,
CONSTRUCTOR,
BREAK,
CONTINUE,
STR_CONCAT, // strings concatenation
ARITH_ONEARG,
// strings concatenation
STR_CONCAT,
TERNARY,
ARGS, // just generate arguments
// just generate one argument
ONE_ARG,
PHI,
NEW_MULTIDIM_ARRAY // TODO: now multidimensional arrays created using Array.newInstance function
// TODO: now multidimensional arrays created using Array.newInstance function
NEW_MULTIDIM_ARRAY
}
@@ -44,6 +44,26 @@ public class InvokeNode extends InsnNode {
return mth;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof InvokeNode) || !super.equals(obj)) {
return false;
}
InvokeNode that = (InvokeNode) obj;
return type == that.type && mth.equals(that.mth);
}
@Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + type.hashCode();
result = 31 * result + mth.hashCode();
return result;
}
@Override
public String toString() {
return InsnUtils.formatOffset(offset) + ": "
@@ -0,0 +1,38 @@
package jadx.core.dex.instructions;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.utils.Utils;
public class PhiInsn extends InsnNode {
public PhiInsn(int regNum, int predecessors) {
super(InsnType.PHI, predecessors);
setResult(InsnArg.reg(regNum, ArgType.UNKNOWN));
for (int i = 0; i < predecessors; i++) {
addReg(regNum, ArgType.UNKNOWN);
}
add(AFlag.DONT_INLINE);
}
@Override
public RegisterArg getArg(int n) {
return (RegisterArg) super.getArg(n);
}
public boolean removeArg(RegisterArg arg) {
boolean isRemoved = super.removeArg(arg);
if (isRemoved) {
arg.getSVar().setUsedInPhi(null);
}
return isRemoved;
}
@Override
public String toString() {
return "PHI: " + getResult() + " = " + Utils.listToString(getArguments());
}
}
@@ -36,6 +36,29 @@ public class SwitchNode extends InsnNode {
return def;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof SwitchNode) || !super.equals(obj)) {
return false;
}
SwitchNode that = (SwitchNode) obj;
return def == that.def
&& Arrays.equals(keys, that.keys)
&& Arrays.equals(targets, that.targets);
}
@Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + Arrays.hashCode(keys);
result = 31 * result + Arrays.hashCode(targets);
result = 31 * result + def;
return result;
}
@Override
public String toString() {
StringBuilder targ = new StringBuilder();
@@ -9,6 +9,8 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.jetbrains.annotations.Nullable;
public abstract class ArgType {
public static final ArgType INT = primitive(PrimitiveType.INT);
@@ -48,6 +50,10 @@ public abstract class ArgType {
ArgType.clsp = clsp;
}
public static boolean isClspSet() {
return ArgType.clsp != null;
}
private static ArgType primitive(PrimitiveType stype) {
return new PrimitiveArg(stype);
}
@@ -89,10 +95,28 @@ public abstract class ArgType {
}
private abstract static class KnownType extends ArgType {
private static final PrimitiveType[] EMPTY_POSSIBLES = new PrimitiveType[0];
@Override
public boolean isTypeKnown() {
return true;
}
@Override
public boolean contains(PrimitiveType type) {
return getPrimitiveType() == type;
}
@Override
public ArgType selectFirst() {
return null;
}
@Override
public PrimitiveType[] getPossibleTypes() {
return EMPTY_POSSIBLES;
}
}
private static final class PrimitiveArg extends KnownType {
@@ -174,7 +198,7 @@ public abstract class ArgType {
private final int bounds;
public WildcardType(ArgType obj, int bound) {
super(ArgType.OBJECT.getObject());
super(OBJECT.getObject());
this.type = obj;
this.bounds = bound;
}
@@ -214,7 +238,7 @@ public abstract class ArgType {
if (bounds == 0) {
return "?";
}
return "? " + (bounds == -1 ? "super" : "extends") + " " + type.toString();
return "? " + (bounds == -1 ? "super" : "extends") + " " + type;
}
}
@@ -265,6 +289,7 @@ public abstract class ArgType {
}
private static final class ArrayArg extends KnownType {
public static final PrimitiveType[] ARRAY_POSSIBLES = new PrimitiveType[]{PrimitiveType.ARRAY};
private final ArgType arrayElement;
public ArrayArg(ArgType arrayElement) {
@@ -287,6 +312,21 @@ public abstract class ArgType {
return PrimitiveType.ARRAY;
}
@Override
public boolean isTypeKnown() {
return arrayElement.isTypeKnown();
}
@Override
public ArgType selectFirst() {
return array(arrayElement.selectFirst());
}
@Override
public PrimitiveType[] getPossibleTypes() {
return ARRAY_POSSIBLES;
}
@Override
public int getArrayDimension() {
return 1 + arrayElement.getArrayDimension();
@@ -339,8 +379,10 @@ public abstract class ArgType {
@Override
public ArgType selectFirst() {
PrimitiveType f = possibleTypes[0];
if (f == PrimitiveType.OBJECT || f == PrimitiveType.ARRAY) {
return object(Consts.CLASS_OBJECT);
if (contains(PrimitiveType.OBJECT)) {
return OBJECT;
} else if (contains(PrimitiveType.ARRAY)) {
return array(OBJECT);
} else {
return primitive(f);
}
@@ -398,7 +440,7 @@ public abstract class ArgType {
}
/**
* @see jadx.core.dex.instructions.args.ArgType.WildcardType#getWildcardBounds()
* @see WildcardType#getWildcardBounds()
*/
public int getWildcardBounds() {
return 0;
@@ -424,20 +466,15 @@ public abstract class ArgType {
return this;
}
public boolean contains(PrimitiveType type) {
throw new UnsupportedOperationException();
}
public abstract boolean contains(PrimitiveType type);
public ArgType selectFirst() {
throw new UnsupportedOperationException();
}
public abstract ArgType selectFirst();
public PrimitiveType[] getPossibleTypes() {
return null;
}
public abstract PrimitiveType[] getPossibleTypes();
@Nullable
public static ArgType merge(ArgType a, ArgType b) {
if (b == null || a == null) {
if (a == null || b == null) {
return null;
}
if (a.equals(b)) {
@@ -454,13 +491,18 @@ public abstract class ArgType {
if (a == UNKNOWN) {
return b;
}
if (a.isArray()) {
return mergeArrays((ArrayArg) a, b);
} else if (b.isArray()) {
return mergeArrays((ArrayArg) b, a);
}
if (!a.isTypeKnown()) {
if (b.isTypeKnown()) {
if (a.contains(b.getPrimitiveType())) {
return b;
} else {
return null;
}
return null;
} else {
// both types unknown
List<PrimitiveType> types = new ArrayList<PrimitiveType>();
@@ -471,7 +513,8 @@ public abstract class ArgType {
}
if (types.isEmpty()) {
return null;
} else if (types.size() == 1) {
}
if (types.size() == 1) {
PrimitiveType nt = types.get(0);
if (nt == PrimitiveType.OBJECT || nt == PrimitiveType.ARRAY) {
return unknown(nt);
@@ -495,31 +538,15 @@ public abstract class ArgType {
String bObj = b.getObject();
if (aObj.equals(bObj)) {
return a.getGenericTypes() != null ? a : b;
} else if (aObj.equals(Consts.CLASS_OBJECT)) {
}
if (aObj.equals(Consts.CLASS_OBJECT)) {
return b;
} else if (bObj.equals(Consts.CLASS_OBJECT)) {
}
if (bObj.equals(Consts.CLASS_OBJECT)) {
return a;
} else {
// different objects
String obj = clsp.getCommonAncestor(aObj, bObj);
return obj == null ? null : object(obj);
}
}
if (a.isArray()) {
if (b.isArray()) {
ArgType ea = a.getArrayElement();
ArgType eb = b.getArrayElement();
if (ea.isPrimitive() && eb.isPrimitive()) {
return OBJECT;
} else {
ArgType res = merge(ea, eb);
return res == null ? null : ArgType.array(res);
}
} else if (b.equals(OBJECT)) {
return OBJECT;
} else {
return null;
}
String obj = clsp.getCommonAncestor(aObj, bObj);
return obj == null ? null : object(obj);
}
if (a.isPrimitive() && b.isPrimitive() && a.getRegCount() == b.getRegCount()) {
return primitive(PrimitiveType.getSmaller(a.getPrimitiveType(), b.getPrimitiveType()));
@@ -528,6 +555,25 @@ public abstract class ArgType {
return null;
}
private static ArgType mergeArrays(ArrayArg array, ArgType b) {
if (b.isArray()) {
ArgType ea = array.getArrayElement();
ArgType eb = b.getArrayElement();
if (ea.isPrimitive() && eb.isPrimitive()) {
return OBJECT;
}
ArgType res = merge(ea, eb);
return res == null ? null : array(res);
}
if (b.contains(PrimitiveType.ARRAY)) {
return array;
}
if (b.equals(OBJECT)) {
return OBJECT;
}
return null;
}
public static boolean isCastNeeded(ArgType from, ArgType to) {
if (from.equals(to)) {
return false;
@@ -539,6 +585,16 @@ public abstract class ArgType {
return true;
}
public static boolean isInstanceOf(ArgType type, ArgType of) {
if (type.equals(of)) {
return true;
}
if (!type.isObject() || !of.isObject()) {
return false;
}
return clsp.isImplements(type.getObject(), of.getObject());
}
public static ArgType parse(String type) {
char f = type.charAt(0);
switch (f) {
@@ -2,12 +2,16 @@ package jadx.core.dex.instructions.args;
import jadx.core.dex.info.FieldInfo;
// TODO: don't extend RegisterArg (now used as a result of instruction)
public final class FieldArg extends RegisterArg {
private final FieldInfo field;
// instArg equal 'null' for static fields
private final InsnArg instArg;
public FieldArg(FieldInfo field, int regNum) {
super(regNum, field.getType());
public FieldArg(FieldInfo field, InsnArg reg) {
super(-1);
this.instArg = reg;
this.field = field;
}
@@ -15,8 +19,12 @@ public final class FieldArg extends RegisterArg {
return field;
}
public InsnArg getInstanceArg() {
return instArg;
}
public boolean isStatic() {
return regNum == -1;
return instArg == null;
}
@Override
@@ -29,6 +37,37 @@ public final class FieldArg extends RegisterArg {
return false;
}
@Override
public void setType(ArgType type) {
this.type = type;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof FieldArg) || !super.equals(obj)) {
return false;
}
FieldArg fieldArg = (FieldArg) obj;
if (!field.equals(fieldArg.field)) {
return false;
}
if (instArg != null ? !instArg.equals(fieldArg.instArg) : fieldArg.instArg != null) {
return false;
}
return true;
}
@Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + field.hashCode();
result = 31 * result + (instArg != null ? instArg.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "(" + field + ")";
@@ -1,27 +0,0 @@
package jadx.core.dex.instructions.args;
public class ImmutableTypedVar extends TypedVar {
public ImmutableTypedVar(ArgType type) {
super(type);
}
@Override
public boolean isImmutable() {
return true;
}
@Override
public void forceSetType(ArgType newType) {
}
@Override
public boolean merge(TypedVar typedVar) {
return false;
}
@Override
public boolean merge(ArgType type) {
return false;
}
}
@@ -1,8 +1,12 @@
package jadx.core.dex.instructions.args;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.utils.InsnUtils;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -26,10 +30,12 @@ public abstract class InsnArg extends Typed {
return reg(InsnUtils.getArg(insn, argNum), type);
}
public static RegisterArg immutableReg(int regNum, ArgType type) {
RegisterArg r = new RegisterArg(regNum);
r.forceSetTypedVar(new ImmutableTypedVar(type));
return r;
public static TypeImmutableArg typeImmutableReg(int regNum, ArgType type) {
return new TypeImmutableArg(regNum, type);
}
public static RegisterArg reg(int regNum, ArgType type, boolean typeImmutable) {
return typeImmutable ? new TypeImmutableArg(regNum, type) : new RegisterArg(regNum, type);
}
public static LiteralArg lit(long literal, ArgType type) {
@@ -78,18 +84,36 @@ public abstract class InsnArg extends Typed {
return null;
}
if (parent == insn) {
LOG.debug("Can't wrap instruction info itself: " + insn);
LOG.debug("Can't wrap instruction info itself: {}", insn);
Thread.dumpStack();
return null;
}
int i = getArgIndex(parent, this);
if (i == -1) {
return null;
}
insn.add(AFlag.WRAPPED);
InsnArg arg = wrapArg(insn);
parent.setArg(i, arg);
return arg;
}
public static void updateParentInsn(InsnNode fromInsn, InsnNode toInsn) {
List<RegisterArg> args = new ArrayList<RegisterArg>();
fromInsn.getRegisterArgs(args);
for (RegisterArg reg : args) {
reg.setParentInsn(toInsn);
}
}
private static int getArgIndex(InsnNode parent, InsnArg arg) {
int count = parent.getArgsCount();
for (int i = 0; i < count; i++) {
if (parent.getArg(i) == this) {
InsnArg arg = wrapArg(insn);
parent.setArg(i, arg);
return arg;
if (parent.getArg(i) == arg) {
return i;
}
}
return null;
return -1;
}
public static InsnArg wrapArg(InsnNode insn) {
@@ -101,11 +125,11 @@ public abstract class InsnArg extends Typed {
break;
case CONST_STR:
arg = wrap(insn);
arg.getTypedVar().forceSetType(ArgType.STRING);
arg.setType(ArgType.STRING);
break;
case CONST_CLASS:
arg = wrap(insn);
arg.getTypedVar().forceSetType(ArgType.CLASS);
arg.setType(ArgType.CLASS);
break;
default:
arg = wrap(insn);
@@ -115,7 +139,7 @@ public abstract class InsnArg extends Typed {
}
public boolean isThis() {
// must be implemented in RegisterArg
// must be implemented in RegisterArg and MthParameterArg
return false;
}
}
@@ -8,7 +8,7 @@ public final class InsnWrapArg extends InsnArg {
public InsnWrapArg(InsnNode insn) {
RegisterArg result = insn.getResult();
this.typedVar = new TypedVar((result != null ? result.getType() : ArgType.VOID));
this.type = result != null ? result.getType() : ArgType.VOID;
this.wrappedInsn = insn;
}
@@ -29,6 +29,6 @@ public final class InsnWrapArg extends InsnArg {
@Override
public String toString() {
return "(wrap: " + typedVar + "\n " + wrappedInsn + ")";
return "(wrap: " + type + "\n " + wrappedInsn + ")";
}
}
@@ -24,7 +24,7 @@ public final class LiteralArg extends InsnArg {
}
}
this.literal = value;
this.typedVar = new TypedVar(type);
this.type = type;
}
public long getLiteral() {
@@ -37,12 +37,12 @@ public final class LiteralArg extends InsnArg {
}
public boolean isInteger() {
PrimitiveType type = typedVar.getType().getPrimitiveType();
return (type == PrimitiveType.INT
PrimitiveType type = this.type.getPrimitiveType();
return type == PrimitiveType.INT
|| type == PrimitiveType.BYTE
|| type == PrimitiveType.CHAR
|| type == PrimitiveType.SHORT
|| type == PrimitiveType.LONG);
|| type == PrimitiveType.LONG;
}
@Override
@@ -69,10 +69,10 @@ public final class LiteralArg extends InsnArg {
if (getType().equals(ArgType.BOOLEAN) && (value.equals("true") || value.equals("false"))) {
return value;
}
return "(" + value + " " + typedVar + ")";
return "(" + value + " " + type + ")";
} catch (JadxRuntimeException ex) {
// can't convert literal to string
return "(" + literal + " " + typedVar + ")";
return "(" + literal + " " + type + ")";
}
}
}
@@ -0,0 +1,8 @@
package jadx.core.dex.instructions.args;
public interface Named {
String getName();
void setName(String name);
}
@@ -1,12 +1,12 @@
package jadx.core.dex.instructions.args;
public final class NamedArg extends InsnArg {
public final class NamedArg extends InsnArg implements Named {
private String name;
public NamedArg(String name, ArgType type) {
this.name = name;
this.typedVar = new TypedVar(type);
this.type = type;
}
public String getName() {
@@ -24,6 +24,6 @@ public final class NamedArg extends InsnArg {
@Override
public String toString() {
return "(" + name + " " + typedVar + ")";
return "(" + name + " " + type + ")";
}
}
@@ -16,7 +16,7 @@ public enum PrimitiveType {
private final String shortName;
private final String longName;
private PrimitiveType(String shortName, String longName) {
PrimitiveType(String shortName, String longName) {
this.shortName = shortName;
this.longName = longName;
}
@@ -1,11 +1,12 @@
package jadx.core.dex.instructions.args;
import jadx.core.dex.attributes.AttributeType;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.instructions.ConstClassNode;
import jadx.core.dex.instructions.ConstStringNode;
import jadx.core.dex.instructions.IndexInsnNode;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.PhiInsn;
import jadx.core.dex.nodes.DexNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.InsnNode;
@@ -14,17 +15,19 @@ import jadx.core.dex.nodes.parser.FieldValueAttr;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class RegisterArg extends InsnArg {
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
private SSAVar sVar;
public RegisterArg(int rn) {
this.regNum = rn;
}
public RegisterArg(int rn, ArgType type) {
this.typedVar = new TypedVar(type);
this.type = type;
this.regNum = rn;
}
@@ -37,18 +40,51 @@ public class RegisterArg extends InsnArg {
return true;
}
public InsnNode getAssignInsn() {
for (InsnArg arg : getTypedVar().getUseList()) {
InsnNode assignInsn = arg.getParentInsn();
if (assignInsn == null) {
// assign as function argument
return null;
} else if (assignInsn.getResult() != null
&& assignInsn.getResult().getRegNum() == regNum) {
return assignInsn;
}
public SSAVar getSVar() {
return sVar;
}
void setSVar(SSAVar sVar) {
this.sVar = sVar;
}
public String getName() {
if (sVar == null) {
return null;
}
return null;
return sVar.getName();
}
public void setName(String name) {
if (sVar != null) {
sVar.setName(name);
}
}
public boolean isNameEquals(InsnArg arg) {
String n = getName();
if (n == null || !(arg instanceof Named)) {
return false;
}
return n.equals(((Named) arg).getName());
}
@Override
public void setType(ArgType type) {
if (sVar != null) {
sVar.setType(type);
}
}
public void mergeDebugInfo(ArgType type, String name) {
setType(type);
setName(name);
}
public RegisterArg duplicate() {
RegisterArg dup = new RegisterArg(getRegNum(), getType());
dup.setSVar(sVar);
return dup;
}
/**
@@ -58,54 +94,75 @@ public class RegisterArg extends InsnArg {
*/
public Object getConstValue(DexNode dex) {
InsnNode parInsn = getAssignInsn();
if (parInsn != null) {
InsnType insnType = parInsn.getType();
switch (insnType) {
case CONST:
return parInsn.getArg(0);
case CONST_STR:
return ((ConstStringNode) parInsn).getString();
case CONST_CLASS:
return ((ConstClassNode) parInsn).getClsType();
case SGET:
FieldInfo f = (FieldInfo) ((IndexInsnNode) parInsn).getIndex();
FieldNode fieldNode = dex.resolveField(f);
if (fieldNode != null) {
FieldValueAttr attr = (FieldValueAttr) fieldNode.getAttributes().get(AttributeType.FIELD_VALUE);
if (attr != null) {
return attr.getValue();
}
} else {
LOG.warn("Field {} not found in dex {}", f, dex);
if (parInsn == null) {
return null;
}
InsnType insnType = parInsn.getType();
switch (insnType) {
case CONST:
return parInsn.getArg(0);
case CONST_STR:
return ((ConstStringNode) parInsn).getString();
case CONST_CLASS:
return ((ConstClassNode) parInsn).getClsType();
case SGET:
FieldInfo f = (FieldInfo) ((IndexInsnNode) parInsn).getIndex();
FieldNode fieldNode = dex.resolveField(f);
if (fieldNode != null) {
FieldValueAttr attr = fieldNode.get(AType.FIELD_VALUE);
if (attr != null) {
return attr.getValue();
}
break;
}
} else {
LOG.warn("Field {} not found in dex {}", f, dex);
}
break;
}
return null;
}
@Override
public boolean isThis() {
if (isRegister()) {
String name = getTypedVar().getName();
if ("this".equals(name)) {
return true;
}
// maybe it was moved from 'this' register
InsnNode ai = getAssignInsn();
if (ai != null && ai.getType() == InsnType.MOVE) {
InsnArg arg = ai.getArg(0);
if (arg != this && arg.isThis()) {
return true;
}
if ("this".equals(getName())) {
return true;
}
// maybe it was moved from 'this' register
InsnNode ai = getAssignInsn();
if (ai != null && ai.getType() == InsnType.MOVE) {
InsnArg arg = ai.getArg(0);
if (arg != this) {
return arg.isThis();
}
}
return false;
}
public InsnNode getAssignInsn() {
if (sVar == null) {
return null;
}
return sVar.getAssign().getParentInsn();
}
public InsnNode getPhiAssignInsn() {
PhiInsn usePhi = sVar.getUsedInPhi();
if (usePhi != null) {
return usePhi;
}
InsnNode parent = sVar.getAssign().getParentInsn();
if (parent != null && parent.getType() == InsnType.PHI) {
return parent;
}
return null;
}
public boolean equalRegisterAndType(RegisterArg arg) {
return regNum == arg.regNum && type.equals(arg.type);
}
@Override
public int hashCode() {
return regNum * 31 + typedVar.hashCode();
return (regNum * 31 + type.hashCode()) * 31 + (sVar != null ? sVar.hashCode() : 0);
}
@Override
@@ -116,15 +173,36 @@ public class RegisterArg extends InsnArg {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
if (!(obj instanceof RegisterArg)) {
return false;
}
RegisterArg other = (RegisterArg) obj;
return regNum == other.regNum && typedVar.equals(other.typedVar);
if (regNum != other.regNum) {
return false;
}
if (!type.equals(other.type)) {
return false;
}
if (sVar != null && !sVar.equals(other.getSVar())) {
return false;
}
return true;
}
@Override
public String toString() {
return "(r" + regNum + " " + typedVar + ")";
StringBuilder sb = new StringBuilder();
sb.append("(r");
sb.append(regNum);
if (sVar != null) {
sb.append("_").append(sVar.getVersion());
}
if (getName() != null) {
sb.append(" '").append(getName()).append("'");
}
sb.append(" ");
sb.append(type);
sb.append(")");
return sb.toString();
}
}
@@ -0,0 +1,210 @@
package jadx.core.dex.instructions.args;
import jadx.core.dex.instructions.PhiInsn;
import java.util.ArrayList;
import java.util.List;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class SSAVar {
private final int regNum;
private final int version;
private VarName varName;
private int startUseAddr;
private int endUseAddr;
@NotNull
private RegisterArg assign;
private final List<RegisterArg> useList = new ArrayList<RegisterArg>(2);
@Nullable
private PhiInsn usedInPhi;
private ArgType type;
private boolean typeImmutable;
public SSAVar(int regNum, int v, @NotNull RegisterArg assign) {
this.regNum = regNum;
this.version = v;
this.assign = assign;
assign.setSVar(this);
startUseAddr = -1;
endUseAddr = -1;
}
public int getStartAddr() {
if (startUseAddr == -1) {
calcUsageAddrRange();
}
return startUseAddr;
}
public int getEndAddr() {
if (endUseAddr == -1) {
calcUsageAddrRange();
}
return endUseAddr;
}
private void calcUsageAddrRange() {
int start = Integer.MAX_VALUE;
int end = Integer.MIN_VALUE;
if (assign.getParentInsn() != null) {
int insnAddr = assign.getParentInsn().getOffset();
if (insnAddr >= 0) {
start = Math.min(insnAddr, start);
end = Math.max(insnAddr, end);
}
}
for (RegisterArg arg : useList) {
if (arg.getParentInsn() != null) {
int insnAddr = arg.getParentInsn().getOffset();
if (insnAddr >= 0) {
start = Math.min(insnAddr, start);
end = Math.max(insnAddr, end);
}
}
}
if (start != Integer.MAX_VALUE && end != Integer.MIN_VALUE) {
startUseAddr = start;
endUseAddr = end;
}
}
public int getRegNum() {
return regNum;
}
public int getVersion() {
return version;
}
@NotNull
public RegisterArg getAssign() {
return assign;
}
public void setAssign(@NotNull RegisterArg assign) {
this.assign = assign;
}
public List<RegisterArg> getUseList() {
return useList;
}
public int getUseCount() {
return useList.size();
}
public void use(RegisterArg arg) {
if (arg.getSVar() != null) {
arg.getSVar().removeUse(arg);
}
arg.setSVar(this);
useList.add(arg);
}
public void removeUse(RegisterArg arg) {
for (int i = 0, useListSize = useList.size(); i < useListSize; i++) {
if (useList.get(i) == arg) {
useList.remove(i);
break;
}
}
}
public void setUsedInPhi(@Nullable PhiInsn usedInPhi) {
this.usedInPhi = usedInPhi;
}
@Nullable
public PhiInsn getUsedInPhi() {
return usedInPhi;
}
public boolean isUsedInPhi() {
return usedInPhi != null;
}
public int getVariableUseCount() {
if (usedInPhi == null) {
return useList.size();
}
return useList.size() + usedInPhi.getResult().getSVar().getUseCount();
}
public void setType(ArgType type) {
ArgType acceptedType;
if (typeImmutable) {
// don't change type, just update types in useList
acceptedType = this.type;
} else {
acceptedType = type;
this.type = acceptedType;
}
assign.type = acceptedType;
for (int i = 0, useListSize = useList.size(); i < useListSize; i++) {
useList.get(i).type = acceptedType;
}
}
public void setTypeImmutable(ArgType type) {
setType(type);
this.typeImmutable = true;
}
public boolean isTypeImmutable() {
return typeImmutable;
}
public void setName(String name) {
if (name != null) {
if (varName == null) {
varName = new VarName();
}
varName.setName(name);
}
}
public String getName() {
if (varName == null) {
return null;
}
return varName.getName();
}
public VarName getVarName() {
return varName;
}
public void setVarName(VarName varName) {
this.varName = varName;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof SSAVar)) {
return false;
}
SSAVar ssaVar = (SSAVar) o;
return regNum == ssaVar.regNum && version == ssaVar.version;
}
@Override
public int hashCode() {
return 31 * regNum + version;
}
@Override
public String toString() {
return "r" + regNum + "_" + version;
}
}
@@ -0,0 +1,66 @@
package jadx.core.dex.instructions.args;
public class TypeImmutableArg extends RegisterArg {
private boolean isThis;
public TypeImmutableArg(int rn, ArgType type) {
super(rn, type);
}
@Override
public boolean isTypeImmutable() {
return true;
}
@Override
public void setType(ArgType type) {
// not allowed
}
public void markAsThis() {
this.isThis = true;
}
@Override
public boolean isThis() {
return isThis;
}
@Override
public String getName() {
if (isThis) {
return "this";
}
return super.getName();
}
@Override
void setSVar(SSAVar sVar) {
if (isThis) {
sVar.setName("this");
}
sVar.setTypeImmutable(type);
super.setSVar(sVar);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof TypeImmutableArg)) {
return false;
}
if (!super.equals(obj)) {
return false;
}
TypeImmutableArg that = (TypeImmutableArg) obj;
return isThis == that.isThis;
}
@Override
public int hashCode() {
return 31 * super.hashCode() + (isThis ? 1 : 0);
}
}
@@ -1,71 +1,31 @@
package jadx.core.dex.instructions.args;
import java.util.List;
public abstract class Typed {
TypedVar typedVar;
public TypedVar getTypedVar() {
return typedVar;
}
protected ArgType type;
public ArgType getType() {
return typedVar.getType();
return type;
}
public boolean merge(Typed var) {
return typedVar.merge(var.getTypedVar());
public void setType(ArgType type) {
this.type = type;
}
public boolean merge(ArgType var) {
return typedVar.merge(var);
public boolean isTypeImmutable() {
return false;
}
public void forceSetTypedVar(TypedVar arg) {
this.typedVar = arg;
}
public void mergeDebugInfo(Typed arg) {
merge(arg);
mergeName(arg);
}
protected void mergeName(Typed arg) {
getTypedVar().mergeName(arg.getTypedVar());
}
public boolean replaceTypedVar(Typed var) {
TypedVar curVar = this.typedVar;
TypedVar newVar = var.typedVar;
if (curVar == newVar) {
return false;
public boolean merge(ArgType newType) {
ArgType m = ArgType.merge(type, newType);
if (m != null && !m.equals(type)) {
setType(m);
return true;
}
if (curVar != null) {
if (curVar.isImmutable()) {
moveInternals(newVar, curVar);
} else {
newVar.merge(curVar);
moveInternals(curVar, newVar);
this.typedVar = newVar;
}
} else {
this.typedVar = newVar;
}
return true;
return false;
}
private void moveInternals(TypedVar from, TypedVar to) {
List<InsnArg> curUseList = from.getUseList();
if (curUseList.size() != 0) {
for (InsnArg arg : curUseList) {
if (arg != this) {
arg.forceSetTypedVar(to);
}
}
to.getUseList().addAll(curUseList);
curUseList.clear();
}
to.mergeName(from);
public boolean merge(InsnArg arg) {
return merge(arg.getType());
}
}
@@ -1,116 +0,0 @@
package jadx.core.dex.instructions.args;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class TypedVar {
private ArgType type;
private final List<InsnArg> useList = new ArrayList<InsnArg>(2);
private String name;
public TypedVar(ArgType initType) {
this.type = initType;
}
public ArgType getType() {
return type;
}
/**
* This method must be used very carefully
*/
public void forceSetType(ArgType newType) {
type = newType;
}
public boolean merge(TypedVar typedVar) {
return merge(typedVar.getType());
}
public boolean merge(ArgType mtype) {
ArgType res = ArgType.merge(type, mtype);
if (res != null && !type.equals(res)) {
this.type = res;
return true;
} else {
return false;
}
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<InsnArg> getUseList() {
return useList;
}
public void removeUse(InsnArg arg) {
Iterator<InsnArg> it = useList.iterator();
while (it.hasNext()) {
InsnArg use = it.next();
if (use == arg) {
it.remove();
}
}
}
public void mergeName(TypedVar arg) {
String argName = arg.getName();
if (argName != null) {
setName(argName);
} else if (getName() != null) {
arg.setName(getName());
}
}
public boolean isImmutable() {
return false;
}
@Override
public int hashCode() {
return type.hashCode() * 31 + (name == null ? 0 : name.hashCode());
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof TypedVar)) {
return false;
}
TypedVar other = (TypedVar) obj;
if (!type.equals(other.type)) {
return false;
}
if (name == null) {
if (other.name != null) {
return false;
}
} else if (!name.equals(other.name)) {
return false;
}
return true;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
if (name != null) {
sb.append('\'').append(name).append("' ");
}
sb.append(type);
return sb.toString();
}
}
@@ -0,0 +1,18 @@
package jadx.core.dex.instructions.args;
public class VarName {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return name;
}
}
@@ -14,7 +14,7 @@ public class ConstructorInsn extends InsnNode {
private final CallType callType;
private final RegisterArg instanceArg;
private static enum CallType {
private enum CallType {
CONSTRUCTOR, // just new instance
SUPER, // super call
THIS, // call constructor from other constructor
@@ -26,7 +26,6 @@ public class ConstructorInsn extends InsnNode {
this.callMth = invoke.getCallMth();
ClassInfo classType = callMth.getDeclClass();
instanceArg = (RegisterArg) invoke.getArg(0);
instanceArg.setParentInsn(this);
if (instanceArg.isThis()) {
if (classType.equals(mth.getParentClass().getClassInfo())) {
@@ -42,11 +41,22 @@ public class ConstructorInsn extends InsnNode {
} else {
callType = CallType.CONSTRUCTOR;
setResult(instanceArg);
// convert from 'use' to 'assign'
instanceArg.getSVar().setAssign(instanceArg);
}
instanceArg.getSVar().removeUse(instanceArg);
for (int i = 1; i < invoke.getArgsCount(); i++) {
addArg(invoke.getArg(i));
}
offset = invoke.getOffset();
setSourceLine(invoke.getSourceLine());
}
public ConstructorInsn(MethodInfo callMth, CallType callType, RegisterArg instanceArg) {
super(InsnType.CONSTRUCTOR, callMth.getArgsCount());
this.callMth = callMth;
this.callType = callType;
this.instanceArg = instanceArg;
}
public MethodInfo getCallMth() {
@@ -61,6 +71,14 @@ public class ConstructorInsn extends InsnNode {
return callMth.getDeclClass();
}
public CallType getCallType() {
return callType;
}
public boolean isNewInstance() {
return callType == CallType.CONSTRUCTOR;
}
public boolean isSuper() {
return callType == CallType.SUPER;
}
@@ -73,6 +91,29 @@ public class ConstructorInsn extends InsnNode {
return callType == CallType.SELF;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof ConstructorInsn) || !super.equals(o)) {
return false;
}
ConstructorInsn that = (ConstructorInsn) o;
return callMth.equals(that.callMth)
&& callType == that.callType
&& instanceArg.equals(that.instanceArg);
}
@Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + callMth.hashCode();
result = 31 * result + callType.hashCode();
result = 31 * result + instanceArg.hashCode();
return result;
}
@Override
public String toString() {
return super.toString() + " " + callMth + " " + callType;
@@ -5,13 +5,19 @@ import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.LiteralArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.regions.IfCondition;
import jadx.core.dex.regions.conditions.IfCondition;
import jadx.core.utils.InsnUtils;
import jadx.core.utils.Utils;
public class TernaryInsn extends InsnNode {
import java.util.List;
private final IfCondition condition;
public final class TernaryInsn extends InsnNode {
private IfCondition condition;
public TernaryInsn(IfCondition condition, RegisterArg result) {
this(condition, result, LiteralArg.TRUE, LiteralArg.FALSE);
}
public TernaryInsn(IfCondition condition, RegisterArg result, InsnArg th, InsnArg els) {
super(InsnType.TERNARY, 2);
@@ -33,6 +39,43 @@ public class TernaryInsn extends InsnNode {
return condition;
}
public void simplifyCondition() {
condition = IfCondition.simplify(condition);
if (condition.getMode() == IfCondition.Mode.NOT) {
invert();
}
}
private void invert() {
condition = IfCondition.invert(condition);
InsnArg tmp = getArg(0);
setArg(0, getArg(1));
setArg(1, tmp);
}
@Override
public void getRegisterArgs(List<RegisterArg> list) {
super.getRegisterArgs(list);
list.addAll(condition.getRegisterArgs());
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof TernaryInsn) || !super.equals(obj)) {
return false;
}
TernaryInsn that = (TernaryInsn) obj;
return condition.equals(that.condition);
}
@Override
public int hashCode() {
return 31 * super.hashCode() + condition.hashCode();
}
@Override
public String toString() {
return InsnUtils.formatOffset(offset) + ": TERNARY"
@@ -1,15 +1,15 @@
package jadx.core.dex.nodes;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.AttrNode;
import jadx.core.dex.attributes.AttributeFlag;
import jadx.core.dex.attributes.AttributeType;
import jadx.core.dex.attributes.BlockRegState;
import jadx.core.dex.attributes.LoopAttr;
import jadx.core.dex.attributes.nodes.LoopInfo;
import jadx.core.utils.InsnUtils;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
public class BlockNode extends AttrNode implements IBlock {
@@ -29,10 +29,7 @@ public class BlockNode extends AttrNode implements IBlock {
// immediate dominator
private BlockNode idom;
// blocks on which dominates this block
private final List<BlockNode> dominatesOn = new ArrayList<BlockNode>(1);
private BlockRegState startState;
private BlockRegState endState;
private List<BlockNode> dominatesOn = Collections.emptyList();
public BlockNode(int id, int offset) {
this.id = id;
@@ -64,9 +61,17 @@ public class BlockNode extends AttrNode implements IBlock {
}
public void lock() {
cleanSuccessors = Collections.unmodifiableList(cleanSuccessors);
successors = Collections.unmodifiableList(successors);
predecessors = Collections.unmodifiableList(predecessors);
cleanSuccessors = lockList(cleanSuccessors);
successors = lockList(successors);
predecessors = lockList(predecessors);
dominatesOn = lockList(dominatesOn);
}
List<BlockNode> lockList(List<BlockNode> list) {
if (list.isEmpty()) {
return Collections.emptyList();
}
return Collections.unmodifiableList(list);
}
/**
@@ -74,26 +79,32 @@ public class BlockNode extends AttrNode implements IBlock {
*/
private static List<BlockNode> cleanSuccessors(BlockNode block) {
List<BlockNode> sucList = block.getSuccessors();
List<BlockNode> nodes = new ArrayList<BlockNode>(sucList.size());
LoopAttr loop = (LoopAttr) block.getAttributes().get(AttributeType.LOOP);
if (loop == null) {
for (BlockNode b : sucList) {
if (!b.getAttributes().contains(AttributeType.EXC_HANDLER)) {
nodes.add(b);
}
}
} else {
for (BlockNode b : sucList) {
if (!b.getAttributes().contains(AttributeType.EXC_HANDLER)) {
// don't follow back edge
if (loop.getStart() == b && loop.getEnd() == block) {
continue;
}
nodes.add(b);
if (sucList.isEmpty()) {
return sucList;
}
List<BlockNode> toRemove = new LinkedList<BlockNode>();
for (BlockNode b : sucList) {
if (b.contains(AType.EXC_HANDLER)) {
toRemove.add(b);
} else if (b.contains(AFlag.SYNTHETIC)) {
List<BlockNode> s = b.getSuccessors();
if (s.size() == 1 && s.get(0).contains(AType.EXC_HANDLER)) {
toRemove.add(b);
}
}
}
return nodes.size() == sucList.size() ? sucList : nodes;
if (block.contains(AFlag.LOOP_END)) {
List<LoopInfo> loops = block.getAll(AType.LOOP);
for (LoopInfo loop : loops) {
toRemove.add(loop.getStart());
}
}
if (toRemove.isEmpty()) {
return sucList;
}
List<BlockNode> result = new ArrayList<BlockNode>(sucList);
result.removeAll(toRemove);
return result;
}
@Override
@@ -146,28 +157,19 @@ public class BlockNode extends AttrNode implements IBlock {
return dominatesOn;
}
public BlockRegState getStartState() {
return startState;
}
public void setStartState(BlockRegState startState) {
this.startState = startState;
}
public BlockRegState getEndState() {
return endState;
}
public void setEndState(BlockRegState endState) {
this.endState = endState;
public void addDominatesOn(BlockNode block) {
if (dominatesOn.isEmpty()) {
dominatesOn = new LinkedList<BlockNode>();
}
dominatesOn.add(block);
}
public boolean isSynthetic() {
return getAttributes().contains(AttributeFlag.SYNTHETIC);
return contains(AFlag.SYNTHETIC);
}
public boolean isReturnBlock() {
return getAttributes().contains(AttributeFlag.RETURN);
return contains(AFlag.RETURN);
}
@Override
@@ -199,6 +201,11 @@ public class BlockNode extends AttrNode implements IBlock {
return true;
}
@Override
public String baseString() {
return Integer.toString(id);
}
@Override
public String toString() {
return "B:" + id + ":" + InsnUtils.formatOffset(startOffset);
@@ -2,10 +2,11 @@ package jadx.core.dex.nodes;
import jadx.core.Consts;
import jadx.core.codegen.CodeWriter;
import jadx.core.dex.attributes.AttributeType;
import jadx.core.dex.attributes.LineAttrNode;
import jadx.core.dex.attributes.SourceFileAttr;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.annotations.Annotation;
import jadx.core.dex.attributes.nodes.JadxErrorAttr;
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.AccessInfo.AFType;
import jadx.core.dex.info.ClassInfo;
@@ -30,10 +31,10 @@ import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.android.dx.io.ClassData;
import com.android.dx.io.ClassData.Field;
import com.android.dx.io.ClassData.Method;
import com.android.dx.io.ClassDef;
import com.android.dex.ClassData;
import com.android.dex.ClassData.Field;
import com.android.dex.ClassData.Method;
import com.android.dex.ClassDef;
public class ClassNode extends LineAttrNode implements ILoadable {
private static final Logger LOG = LoggerFactory.getLogger(ClassNode.class);
@@ -103,15 +104,16 @@ public class ClassNode extends LineAttrNode implements ILoadable {
int sfIdx = cls.getSourceFileIndex();
if (sfIdx != DexNode.NO_INDEX) {
String fileName = dex.getString(sfIdx);
if (!this.getFullName().contains(fileName.replace(".java", ""))) {
this.getAttributes().add(new SourceFileAttr(fileName));
if (!this.getFullName().contains(fileName.replace(".java", ""))
&& !fileName.equals("SourceFile")) {
this.addAttr(new SourceFileAttr(fileName));
LOG.debug("Class '{}' compiled from '{}'", this, fileName);
}
}
// restore original access flags from dalvik annotation if present
int accFlagsValue;
Annotation a = getAttributes().getAnnotation(Consts.DALVIK_INNER_CLASS);
Annotation a = getAnnotation(Consts.DALVIK_INNER_CLASS);
if (a != null) {
accFlagsValue = (Integer) a.getValues().get("accessFlags");
} else {
@@ -130,7 +132,7 @@ public class ClassNode extends LineAttrNode implements ILoadable {
try {
new AnnotationsParser(this).parse(offset);
} catch (DecodeException e) {
LOG.error("Error parsing annotations in " + this, e);
LOG.error("Error parsing annotations in {}", this, e);
}
}
}
@@ -138,8 +140,7 @@ public class ClassNode extends LineAttrNode implements ILoadable {
private void loadStaticValues(ClassDef cls, List<FieldNode> staticFields) throws DecodeException {
for (FieldNode f : staticFields) {
if (f.getAccessFlags().isFinal()) {
FieldValueAttr nullValue = new FieldValueAttr(null);
f.getAttributes().add(nullValue);
f.addAttr(new FieldValueAttr(null));
}
}
@@ -151,7 +152,7 @@ public class ClassNode extends LineAttrNode implements ILoadable {
for (FieldNode f : staticFields) {
AccessInfo accFlags = f.getAccessFlags();
if (accFlags.isStatic() && accFlags.isFinal()) {
FieldValueAttr fv = (FieldValueAttr) f.getAttributes().get(AttributeType.FIELD_VALUE);
FieldValueAttr fv = f.get(AType.FIELD_VALUE);
if (fv != null && fv.getValue() != null) {
if (accFlags.isPublic()) {
dex.getConstFields().put(fv.getValue(), f);
@@ -183,7 +184,7 @@ public class ClassNode extends LineAttrNode implements ILoadable {
}
}
} catch (JadxRuntimeException e) {
LOG.error("Class signature parse error: " + this, e);
LOG.error("Class signature parse error: {}", this, e);
}
}
@@ -197,16 +198,21 @@ public class ClassNode extends LineAttrNode implements ILoadable {
field.setType(gType);
}
} catch (JadxRuntimeException e) {
LOG.error("Field signature parse error: " + field, e);
LOG.error("Field signature parse error: {}", field, e);
}
}
}
}
@Override
public void load() throws DecodeException {
public void load() {
for (MethodNode mth : getMethods()) {
mth.load();
try {
mth.load();
} catch (DecodeException e) {
LOG.error("Method load error", e);
mth.addAttr(new JadxErrorAttr(e));
}
}
for (ClassNode innerCls : getInnerClasses()) {
innerCls.load();
@@ -348,6 +354,11 @@ public class ClassNode extends LineAttrNode implements ILoadable {
return parentClass;
}
public ClassNode getTopParentClass() {
ClassNode parent = getParentClass();
return parent == this ? this : parent.getParentClass();
}
public List<ClassNode> getInnerClasses() {
return innerClasses;
}
@@ -371,10 +382,7 @@ public class ClassNode extends LineAttrNode implements ILoadable {
public MethodNode getDefaultConstructor() {
for (MethodNode mth : methods) {
if (mth.getAccessFlags().isConstructor()
&& mth.getMethodInfo().isConstructor()
&& (mth.getMethodInfo().getArgsCount() == 0
|| (mth.getArguments(false) != null && mth.getArguments(false).isEmpty()))) {
if (mth.isDefaultConstructor()) {
return mth;
}
}
@@ -13,34 +13,32 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.android.dx.io.ClassData;
import com.android.dx.io.ClassData.Method;
import com.android.dx.io.ClassDef;
import com.android.dx.io.Code;
import com.android.dx.io.DexBuffer;
import com.android.dx.io.DexBuffer.Section;
import com.android.dx.io.FieldId;
import com.android.dx.io.MethodId;
import com.android.dx.io.ProtoId;
import com.android.dx.merge.TypeList;
import org.jetbrains.annotations.Nullable;
import com.android.dex.ClassData;
import com.android.dex.ClassData.Method;
import com.android.dex.ClassDef;
import com.android.dex.Code;
import com.android.dex.Dex;
import com.android.dex.Dex.Section;
import com.android.dex.FieldId;
import com.android.dex.MethodId;
import com.android.dex.ProtoId;
import com.android.dex.TypeList;
public class DexNode {
public static final int NO_INDEX = -1;
private final RootNode root;
private final DexBuffer dexBuf;
private final Dex dexBuf;
private final List<ClassNode> classes = new ArrayList<ClassNode>();
private final String[] strings;
private final Map<Object, FieldNode> constFields = new HashMap<Object, FieldNode>();
public DexNode(RootNode root, InputFile input) {
this.root = root;
this.dexBuf = input.getDexBuffer();
List<String> stringList = dexBuf.strings();
this.strings = stringList.toArray(new String[stringList.size()]);
}
public void loadClasses() throws DecodeException {
@@ -53,10 +51,12 @@ public class DexNode {
return classes;
}
@Nullable
public ClassNode resolveClass(ClassInfo clsInfo) {
return root.resolveClass(clsInfo);
}
@Nullable
public MethodNode resolveMethod(MethodInfo mth) {
ClassNode cls = resolveClass(mth.getDeclClass());
if (cls != null) {
@@ -65,6 +65,7 @@ public class DexNode {
return null;
}
@Nullable
public FieldNode resolveField(FieldInfo field) {
ClassNode cls = resolveClass(field.getDeclClass());
if (cls != null) {
@@ -80,7 +81,7 @@ public class DexNode {
// DexBuffer wrappers
public String getString(int index) {
return strings[index];
return dexBuf.strings().get(index);
}
public ArgType getType(int index) {

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