Compare commits

...

134 Commits

Author SHA1 Message Date
Skylot 6bac5c162e core: select correct array type element 2013-12-25 23:55:45 +04:00
Skylot 5cbf71bde6 core: remove unnecessary return instructions for void methods 2013-12-25 23:49:40 +04:00
Skylot a85d382e89 core: reformat TryCatchBlock class 2013-12-24 22:38:34 +04:00
Skylot 4caa58f5fd core: use correct argument wrap method 2013-12-24 22:23:12 +04:00
Skylot 43913d47ec core: fix method definition 2013-12-24 22:20:48 +04:00
Skylot 9f51cabf69 core: fix anonymous class codegen 2013-12-20 23:22:27 +04:00
Skylot 1c60e5e315 core: inline anonymous classes 2013-12-13 17:50:41 +04:00
Skylot a9290f3131 core: remove synthetic constructors 2013-12-13 17:50:41 +04:00
Skylot e46dfc555e core: redone return blocks splitting for fix issue #4 2013-12-13 17:50:33 +04:00
Skylot e54b764588 fix code style issues reported by sonar 2013-12-07 16:11:12 +04:00
Skylot 37f03bcf9e add drone.io for download artifacts 2013-12-07 00:16:45 +04:00
Skylot 1d0f23dfbb update gradle to 1.9 2013-12-07 00:15:10 +04:00
Skylot 30355cc9d6 core: remove synthetic fields for inner classes 2013-12-06 23:42:04 +04:00
Skylot ed67f8e118 core: make strict shrink code implementation 2013-12-06 16:37:30 +04:00
Skylot 4531256005 add sonar code checking 2013-12-06 16:27:34 +04:00
Skylot 662ebb6451 core: don't add super call without args 2013-11-29 15:35:04 +04:00
Skylot 4a63f52259 gui: fix ui tabs handling 2013-11-27 23:00:15 +04:00
Skylot c416f77e99 core: fix android specific class handler 2013-11-27 22:14:34 +04:00
skylot fde431d131 Merge pull request #3 from 13-beta2/master
Core fix
2013-11-27 10:04:22 -08:00
13.beta2 272e0d3754 core: fix missing code after 'if' inside loop 2013-11-26 23:56:17 +04:00
Skylot b44a1e3a4f gui: fix tab selection 2013-11-26 22:14:49 +04:00
Skylot b18dabee15 gui: adjust tabbed ui appearance 2013-11-24 17:54:05 +04:00
Skylot b6befbdcf2 gui: initial tabbed ui implementation 2013-11-21 00:15:13 +04:00
Skylot 2cfc208aa9 core: fix constant fields values retrieval 2013-11-18 21:06:45 +04:00
13.beta2 132b8d0618 core: inlining invoke arguments
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
setContentView(2130837505);
-->
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
setContentView(R.layout.samplelayout);
2013-11-16 17:01:46 +04:00
skylot 5dc4c28da5 Merge pull request #2 from 13-beta2/master
Handy core improvement
2013-11-15 11:00:01 -08:00
13.beta2 7342ae18a6 core: fix to 4f61ddd 2013-11-15 22:31:44 +04:00
13.beta2 eafe080c41 core: omit redundant brackets in case blocks 2013-11-14 01:36:46 +04:00
13.beta2 4f61ddd4b7 core: inlining return results
int r0i;
switch(arg0) {
case 1:
r0i = 255;
return r0i;
}
r0i = 128;
return r0i;
-->
switch(arg0) {
case 1:
return 255;
}
return 128;
2013-11-14 01:36:44 +04:00
13.beta2 86b0458673 core: replace switch labels with matched static final fields, searching up to root ClassNode 2013-11-14 01:36:42 +04:00
Skylot 36cfc9d189 core tests: add inner classes in internal tests 2013-11-12 23:24:21 +04:00
Skylot b2f189b572 core: process complex condition in loop header 2013-11-12 21:00:05 +04:00
Skylot eec524ad85 core: make methods arguments types immutable 2013-11-10 14:15:29 +04:00
Skylot d94087b939 core: fix encoded value parser for signed and floating point numbers 2013-10-23 23:27:53 +04:00
Skylot 1ba19d3600 core: fix annotations number decoder 2013-10-22 22:37:53 +04:00
Skylot 07402ba4c0 core: fix "null" enum field 2013-10-19 18:54:03 +04:00
Skylot d60698206e core: fix type in fill-array instruction 2013-09-29 19:36:56 +04:00
Skylot c59b65e71c build: add 'dist' task 2013-09-28 15:51:45 +04:00
Skylot bd4c61d300 core: fix incorrect float values processing 2013-09-28 15:17:20 +04:00
Skylot 00a6b6efd2 core: add tests options, change log format 2013-09-28 13:38:14 +04:00
Skylot 04ac3b2eb7 core: fix classes import naming 2013-09-26 22:28:27 +04:00
Skylot 6bc2d3321c code refactoring 2013-09-25 18:07:14 +04:00
Skylot c95211925e core: omit 'this' for methods and fields 2013-09-24 23:01:01 +04:00
Skylot 95e9da36c5 core: simplify conditions, omit redundant parenthesis 2013-09-24 22:58:32 +04:00
Skylot 9bf7270bf3 reformat code, resolve compiler warnings 2013-09-24 21:59:32 +04:00
Skylot a99e0e9618 upgrade to gradle 1.8 2013-09-24 21:46:43 +04:00
Skylot 01c4706013 core: improve chained conditions processing 2013-09-23 23:19:27 +04:00
Skylot 89c7b9a848 core: fix ArgType.equals 2013-09-21 22:23:50 +04:00
Skylot 1358a05a74 core: omit redundant brackets in conditions 2013-09-21 19:32:10 +04:00
Skylot 1b0a8990f7 core: move tests 2013-09-21 19:14:51 +04:00
Skylot 4edfffae27 core: remove not needed casts 2013-09-14 17:38:38 +04:00
Skylot cde8d72510 core: don't add redundant brackets 2013-09-12 23:32:47 +04:00
Skylot d7ce0245f6 core: convert arithmetic operations on field to arith instruction 2013-09-09 23:30:10 +04:00
Skylot 49c5ceb06e core: add framework for internal tests 2013-09-09 23:21:47 +04:00
Skylot 4c03a4245b improve jadx api 2013-09-08 16:51:17 +04:00
Skylot 4454e013c4 gui: add internal tests 2013-09-04 23:23:16 +04:00
Skylot 1e7546f4a3 update tests 2013-09-04 23:23:16 +04:00
Skylot 7742d34111 decrease memory requirements 2013-09-04 23:23:16 +04:00
Skylot a413aaf140 update gradle to 1.7 2013-09-04 23:23:07 +04:00
Skylot e94396532e gui: open file selection dialog on start 2013-08-11 00:05:09 +04:00
Skylot cc1be673e7 core: sort classes in package and methods 2013-08-11 00:03:59 +04:00
Skylot f9e87d4da0 gui: set bigger window size at start 2013-08-10 23:34:49 +04:00
Skylot ab8fa23fc3 cli: move specific code from common jadx args 2013-08-10 22:52:06 +04:00
Skylot 7985466213 add Travis CI integration 2013-08-06 22:24:02 +04:00
Skylot e92ed48502 samples: remove generated code from gradle source set 2013-08-03 15:56:33 +04:00
Skylot c508e72c19 core: fixed types for arguments from overloaded methods 2013-08-02 14:00:55 +04:00
Skylot 940de24099 core: split const-string and const-class instructions 2013-08-02 13:59:49 +04:00
Skylot 6ddb71e21f core: add classpath for precise class types resolving 2013-08-01 18:19:54 +04:00
Skylot d0f120c314 core: fix string concatenation 2013-07-31 13:31:41 +04:00
Skylot 54f4c6d2cb build samples without debug info, fix try/catch processing 2013-07-29 18:44:01 +04:00
Skylot 1f21760bbe rename jadx-cli, update build scripts 2013-07-27 15:43:07 +04:00
Skylot 67eb55a95d gui: add type and access info to classes tree 2013-07-26 23:04:42 +04:00
Skylot fa097cc6b2 gui: add action for save all decompiled source 2013-07-26 15:57:29 +04:00
Skylot 34222dae0a gui: add search bar 2013-07-25 23:39:47 +04:00
Skylot 3a62d04376 gui: fix tree class switch 2013-07-24 17:43:32 +04:00
Skylot ca2c935f65 gui: don't create output directory on startup 2013-07-24 17:40:55 +04:00
Skylot ddf2174cae core: fix duplicated imports 2013-07-24 17:40:55 +04:00
Skylot 7096c38299 fix gradle scripts, update readme 2013-07-24 17:40:49 +04:00
Skylot c4cdd8514d gui: add fields and methods to tree 2013-07-23 23:01:48 +04:00
Skylot 25b2c8fe5b core: store line info, add fields and methods to api, refactoring 2013-07-23 22:59:00 +04:00
Skylot 36da79feb8 gui: add icons for packages tree, add hierarchical mode 2013-07-22 22:43:00 +04:00
Skylot 571b5590ac gui: add icons to classes tree 2013-07-21 19:03:13 +04:00
Skylot 7eb5defc2a Update gradle build files 2013-07-10 23:47:45 +04:00
Skylot ce7d6f0156 Add jadx-gui, restructure src directory 2013-07-10 22:57:39 +04:00
Skylot cbbb73355b Remove unneeded 'return' instructions 2013-07-04 23:47:14 +04:00
Skylot f51d633707 Inline all literal constants (not only boolean) 2013-07-03 23:44:17 +04:00
Skylot bca90c1f41 Don't add 'public' for annotations methods 2013-07-03 23:44:17 +04:00
skylot 17c0fd21d2 Publish release on github 2013-07-03 00:03:35 +04:00
Skylot fb43d716d9 Replace constants with matched static final fields 2013-07-01 23:49:19 +04:00
Skylot 3598a1279c Process complex 'if' conditions, refactoring 2013-06-30 22:48:16 +04:00
Skylot 5a40d960b2 Simplify boolean conditions 2013-06-17 23:07:35 +04:00
Skylot d6a468f0fc Don't show not important warnings 2013-06-17 23:06:18 +04:00
Skylot 69eb57cbd7 Fix sythetic methods inline 2013-06-16 22:51:30 +04:00
Skylot e3a10391ee Fix codegen for arith ops, rename reserved arg names in methods 2013-06-16 19:16:26 +04:00
Skylot 8da0ba82e4 Save source file name, move constant strings 2013-06-16 17:04:26 +04:00
Skylot 35ee0a2549 Replace StringBuilder append chain with strings concatenation 2013-06-15 21:39:46 +04:00
Skylot 60615d01c3 Fix issue for wraped synchronized argument 2013-06-15 19:46:01 +04:00
Skylot cb6ff60671 Fix small issues and improve code 2013-06-15 19:16:09 +04:00
Skylot 26800fb790 Refactor increment and decrement operations codegen 2013-06-06 21:48:57 +04:00
Skylot 59292a2bc1 Disable return splice 2013-05-31 00:04:34 +04:00
Skylot b0bcea958c Add IntelliJ Idea files to git ignore 2013-05-31 00:04:34 +04:00
Skylot dfe97b768e Refactoring: extract interface for JadxArgs 2013-05-31 00:04:34 +04:00
skylot b3fa8dbeed Add build status to readme 2013-05-26 16:21:38 +04:00
Skylot 8eae42364f Update gradle 2013-05-25 20:11:10 +04:00
Skylot 81ee9e6b7d Remove unused code 2013-05-25 19:52:06 +04:00
Skylot d5737adec7 Fix 'rsub-int' instruction decoding 2013-05-05 22:11:15 +04:00
Skylot 210c8e547c Adjust types merge, other code improvements 2013-05-04 18:19:34 +04:00
Skylot 4e284c4ce2 Use chars instead strings, code refactoring 2013-05-02 18:27:28 +04:00
Skylot c363bea59f Set debug info for unused variables 2013-05-02 18:27:19 +04:00
Skylot 3fcbca9456 Fix try/catch/finally block processing 2013-05-02 18:18:15 +04:00
Skylot 56eac437f1 Fix errors in AttributesList, refactoring 2013-05-02 18:14:43 +04:00
Skylot b4d08bdc55 Split Main class 2013-05-02 18:13:35 +04:00
Skylot c7ed985767 Various code improvements 2013-04-28 22:55:38 +04:00
Skylot a6f6115184 Fix loops processing 2013-04-28 22:46:42 +04:00
Skylot 533883b5aa Fix arguments types in array-put instruction 2013-04-28 19:10:09 +04:00
Skylot 2e40ca17dc Update gradle and dependencies versions 2013-04-28 14:15:45 +04:00
Skylot 0e04dc72b9 Avoid variable names clash 2013-04-27 21:13:42 +04:00
Skylot 484e07df8d Fix setting variable name from debug info 2013-04-27 21:03:01 +04:00
Skylot a55f4c59ce Fix self constructor call 2013-04-25 21:59:06 +04:00
Skylot 4e7ef9f4d2 Fix 'switch' codegen for empty case block 2013-04-24 23:49:31 +04:00
Skylot e60b599260 Refactoring: remove unused arg in InsnNode 2013-04-21 20:06:56 +04:00
Skylot 96e3e887ce Inline 'access' synthetic methods 2013-04-21 19:57:16 +04:00
Skylot 87794d25c1 Fix bug for args in methods with generics 2013-04-19 22:26:19 +04:00
Skylot c4f2119955 Remove redundant space 2013-04-18 23:40:32 +04:00
Skylot 76feab3f2a Don't remove constructor with super call 2013-04-18 23:36:39 +04:00
Skylot 550659d372 Fix generic types for abstract methods 2013-04-18 23:15:09 +04:00
Skylot ba1524dceb Fix arrays parsing in signature 2013-04-18 22:58:27 +04:00
Skylot 0ee499c54c Add generic types for classes and fields 2013-04-15 23:24:14 +04:00
Skylot 3b84aec57e Add generic types to methods declarations 2013-04-14 21:10:13 +04:00
Skylot cc318b13ad ArgType refactoring, add generic parsing 2013-04-13 00:15:01 +04:00
Skylot d662b2c50c Fix debug info parsing, save generics types for variables 2013-04-09 23:56:20 +04:00
Skylot a617a77d1f Fix variable declaration for multiple assigns 2013-04-08 23:50:40 +04:00
Skylot 62a28c8e88 Remove empty public constructors 2013-04-07 15:39:50 +04:00
Skylot 045a643bba Update bintray download link 2013-03-31 12:03:20 +04:00
308 changed files with 13028 additions and 5185 deletions
+11 -1
View File
@@ -1,15 +1,25 @@
# Eclipse files
.classpath
.project
.settings/
# IntelliJ Idea files
.idea/
out/
*.iml
*.ipr
*.iws
bin/
target/
build/
idea/
.gradle/
gradle.properties
*-tmp/
*.dex
*.jar
*.class
*.dump
*.log
+12
View File
@@ -0,0 +1,12 @@
language: java
jdk:
- oraclejdk7
- openjdk7
- openjdk6
before_install:
- chmod +x gradlew
script:
- TERM=dumb ./gradlew clean build dist
notifications:
email:
- skylot@gmail.com
+40 -1
View File
@@ -2,7 +2,7 @@ The majority of jadx is written and copyrighted by me (Skylot)
and released under the Apache 2.0 license:
*******************************************************************************
Copyright 2013 Skylot
Copyright 2013, Skylot
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -104,3 +104,42 @@ under the terms of the GNU Lesser General Public License version 2.1
as published by the Free Software Foundation.
*******************************************************************************
Jadx-gui components
===================
RSyntaxTextArea library licensed under modified BSD liense:
*******************************************************************************
Copyright (c) 2012, Robert Futrell
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* 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.
* Neither the name of the author 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 <COPYRIGHT HOLDER> 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.
*******************************************************************************
Icons copied from several places:
- Eclipse Project (JDT UI) - licensed under EPL v1.0 (http://www.eclipse.org/legal/epl-v10.html)
- famfamfam silk icon set (http://www.famfamfam.com/lab/icons/silk/) - licensed under Creative Commons Attribution 2.5 License (http://creativecommons.org/licenses/by/2.5/)
+30 -22
View File
@@ -1,42 +1,50 @@
## About
## JADX
**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
### Downloads
Latest version available at
[sourceforge](http://sourceforge.net/projects/jadx/files/)
or
[bintray](http://bintray.com/pkg/show/general/skylot/jadx/jadx-bundle)
- [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/)
### Build
jadx uses [gradle](http://www.gradle.org/) for build:
### Building from source
git clone https://github.com/skylot/jadx.git
cd jadx
./gradlew build
./gradlew dist
(on windows, use `gradlew.bat` instead of `./gradlew`)
(on Windows, use `gradlew.bat` instead of `./gradlew`)
Scripts for run jadx will be placed in `build/jadx/bin`
and also packed to `build/jadx-<version>.zip`
Scripts for run jadx will be placed in `build/install/jadx/bin`
and also packed to `build/distributions/jadx-<version>.zip`
### Run
Run **jadx** on itself:
cd build/install/jadx/
bin/jadx -d out lib/jadx-*.jar
cd build/jadx/
bin/jadx -d out lib/jadx-core-*.jar
#or
bin/jadx-gui lib/jadx-core-*.jar
### Usage
```
jadx [options] <input files> (.dex, .apk, .jar or .class)
jadx[-gui] [options] <input file> (.dex, .apk or .jar)
options:
-d, --output-dir - output directory
-j, --threads-count - processing threads count
-f, --fallback - make simple dump (using goto instead of 'if', 'for', etc)
--not-obfuscated - set this flag if code not obfuscated
--cfg - save methods control flow graph
--raw-cfg - save methods control flow graph (use raw instructions)
-v, --verbose - verbose output
-h, --help - print this help
-d, --output-dir - output directory
-j, --threads-count - processing threads count
-f, --fallback - make simple dump (using goto instead of 'if', 'for', etc)
--cfg - save methods control flow graph to dot file
--raw-cfg - save methods control flow graph (use raw instructions)
-v, --verbose - verbose output
-h, --help - print this help
Example:
jadx -d out classes.dex
```
+52 -76
View File
@@ -1,94 +1,70 @@
apply plugin: 'java'
apply plugin: 'application'
ext.jadxVersion = file('version').readLines().get(0)
apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: 'sonar-runner'
sourceCompatibility = 1.6
targetCompatibility = 1.6
subprojects {
apply plugin: 'java'
apply plugin: 'idea'
apply plugin: 'eclipse'
version = file('version').readLines().get(0)
sourceCompatibility = 1.6
targetCompatibility = 1.6
mainClassName = "jadx.Main"
manifest.mainAttributes("jadx-version" : version)
version = jadxVersion
project.ext {
mainSamplesClass = "jadx.samples.RunTests"
samplesJadxSrcDir = "${buildDir}/samples-jadx/src"
}
dependencies {
compile 'com.google.android.tools:dx:1.7'
compile 'com.beust:jcommander:1.30'
compile 'org.slf4j:slf4j-api:1.6.6'
compile 'ch.qos.logback:logback-classic:1.0.9'
testCompile 'junit:junit:4.8.2'
}
repositories {
mavenCentral()
}
sourceSets {
samples
//TODO don't add to eclipse classpath
samplesJadx {
java {
srcDir samplesJadxSrcDir
output.classesDir "${buildDir}/samples-jadx/output"
gradle.projectsEvaluated {
tasks.withType(Compile) {
if (!"${it}".contains(":jadx-samples:")) {
options.compilerArgs << "-Xlint" << "-Xlint:unchecked" << "-Xlint:deprecation"
}
}
}
}
task samplesRun(type: JavaExec, dependsOn: compileSamplesJava) {
classpath = sourceSets.samples.output
main = mainSamplesClass
}
jar {
version = jadxVersion
manifest {
mainAttributes('jadx-version' : jadxVersion)
}
}
task samplesJar(type: Jar, dependsOn: samplesRun) {
baseName = 'samples'
from sourceSets.samples.output
}
dependencies {
compile 'org.slf4j:slf4j-api:1.7.5'
testCompile 'junit:junit:4.11'
testCompile "org.mockito:mockito-core:1.9.5"
}
task samplesJadxCreate(type: JavaExec, dependsOn: [compileJava, samplesJar]) {
classpath = sourceSets.main.output + configurations.compile
main = mainClassName
args = ['-d', samplesJadxSrcDir, samplesJar.archivePath]
}
compileSamplesJadxJava.dependsOn samplesJadxCreate
task samplesJadxRun(type: JavaExec, dependsOn: compileSamplesJadxJava) {
classpath = sourceSets.samplesJadx.output
main = mainSamplesClass
}
task samples (dependsOn: samplesJadxRun) {
}
//check.dependsOn samples
build.dependsOn distZip
build.dependsOn installApp
startScripts {
doLast {
// increase default max heap size
String var = 'DEFAULT_JVM_OPTS='
String args = '-Xmx1400M'
unixScript.text = unixScript.text.replace(var + '""', var + '"' + args + '"')
windowsScript.text = windowsScript.text.replace(var, var + args)
repositories {
mavenCentral()
}
}
applicationDistribution.with {
into('') {
from '.'
include 'README.md'
include 'NOTICE'
task copyArtifacts(type: Sync, dependsOn: ['jadx-cli:installApp', 'jadx-gui:installApp']) {
destinationDir file("$buildDir/jadx")
['jadx-cli', 'jadx-gui'].each {
from tasks.getByPath(":${it}:installApp").destinationDir
}
}
task pack(type: Zip, dependsOn: copyArtifacts) {
destinationDir buildDir
archiveName "jadx-${jadxVersion}.zip"
from copyArtifacts.destinationDir
}
task dist(dependsOn: pack) {
description = 'Build jadx distribution zip'
}
task samples(dependsOn: 'jadx-samples:samples') {
}
task build(dependsOn: [dist, samples]) {
}
task clean(type: Delete) {
delete buildDir
}
task wrapper(type: Wrapper) {
gradleVersion = '1.4'
gradleVersion = '1.9'
}
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.4-bin.zip
distributionUrl=http\://services.gradle.org/distributions/gradle-1.9-bin.zip
+28
View File
@@ -0,0 +1,28 @@
apply plugin: 'application'
mainClassName = 'jadx.cli.JadxCLI'
applicationName = 'jadx'
dependencies {
compile(project(':jadx-core'))
compile 'com.beust:jcommander:1.30'
}
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)
}
}
applicationDistribution.with {
into('') {
from '../.'
include 'README.md'
include 'NOTICE'
}
}
@@ -0,0 +1,68 @@
package jadx.cli;
import jadx.api.Decompiler;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.exceptions.JadxException;
import java.io.File;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class JadxCLI {
private static final Logger LOG = LoggerFactory.getLogger(JadxCLI.class);
public static void main(String[] args) {
try {
JadxCLIArgs jadxArgs = new JadxCLIArgs(args);
checkArgs(jadxArgs);
processAndSave(jadxArgs);
} catch (Exception e) {
LOG.error(e.getMessage());
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();
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 {
if (jadxArgs.getInput().isEmpty()) {
LOG.error("Please specify input file");
jadxArgs.printUsage();
System.exit(1);
}
File outputDir = jadxArgs.getOutDir();
if (outputDir == null) {
String outDirName;
File file = jadxArgs.getInput().get(0);
String name = file.getName();
int pos = name.lastIndexOf('.');
if (pos != -1) {
outDirName = name.substring(0, pos);
} else {
outDirName = name + "-jadx-out";
}
LOG.info("output directory: " + outDirName);
outputDir = new File(outDirName);
jadxArgs.setOutputDir(outputDir);
}
if (outputDir.exists() && !outputDir.isDirectory()) {
throw new JadxException("Output directory exists as file " + outputDir);
}
}
}
@@ -0,0 +1,184 @@
package jadx.cli;
import jadx.api.IJadxArgs;
import jadx.core.Consts;
import jadx.core.utils.exceptions.JadxException;
import java.io.File;
import java.io.PrintStream;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.beust.jcommander.JCommander;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.ParameterDescription;
import com.beust.jcommander.ParameterException;
public final class JadxCLIArgs implements IJadxArgs {
@Parameter(description = "<input file> (.dex, .apk or .jar)")
protected List<String> files;
@Parameter(names = {"-d", "--output-dir"}, description = "output directory")
protected String outDirName;
@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)
protected boolean fallbackMode = false;
@Parameter(names = {"--cfg"}, description = "save methods control flow graph to dot file")
protected boolean cfgOutput = false;
@Parameter(names = {"--raw-cfg"}, description = "save methods control flow graph (use raw instructions)")
protected boolean rawCfgOutput = false;
@Parameter(names = {"-v", "--verbose"}, description = "verbose output")
protected boolean verbose = false;
@Parameter(names = {"-h", "--help"}, description = "print this help", help = true)
protected boolean printHelp = false;
private final List<File> input = new ArrayList<File>(1);
private File outputDir;
public JadxCLIArgs(String[] args) {
parse(args);
processArgs();
}
private void parse(String[] args) {
try {
new JCommander(this, args);
} catch (ParameterException e) {
System.err.println("Arguments parse error: " + e.getMessage());
printUsage();
System.exit(1);
}
}
public void processArgs() {
if (isPrintHelp()) {
printUsage();
System.exit(0);
}
try {
if (threadsCount <= 0)
throw new JadxException("Threads count must be positive");
if (files != null) {
for (String fileName : files) {
File file = new File(fileName);
if (file.exists())
input.add(file);
else
throw new JadxException("File not found: " + file);
}
}
if (input.size() > 1)
throw new JadxException("Only one input file is supported");
if (outDirName != null)
outputDir = new File(outDirName);
if (isVerbose()) {
ch.qos.logback.classic.Logger rootLogger =
(ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
rootLogger.setLevel(ch.qos.logback.classic.Level.DEBUG);
}
} catch (JadxException e) {
System.err.println("ERROR: " + e.getMessage());
printUsage();
System.exit(1);
}
}
public void printUsage() {
JCommander jc = new JCommander(this);
// 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();
out.println("usage: jadx [options] " + jc.getMainParameterDescription());
out.println("options:");
List<ParameterDescription> params = jc.getParameters();
int maxNamesLen = 0;
for (ParameterDescription p : params) {
int len = p.getNames().length();
if (len > maxNamesLen)
maxNamesLen = len;
}
Field[] fields = this.getClass().getDeclaredFields();
for (Field f : fields) {
for (ParameterDescription p : params) {
String name = f.getName();
if (name.equals(p.getParameterized().getName())) {
StringBuilder opt = new StringBuilder();
opt.append(' ').append(p.getNames());
addSpaces(opt, maxNamesLen - opt.length() + 2);
opt.append("- ").append(p.getDescription());
out.println(opt.toString());
break;
}
}
}
out.println("Example:");
out.println(" jadx -d out classes.dex");
}
private static void addSpaces(StringBuilder str, int count) {
for (int i = 0; i < count; i++)
str.append(' ');
}
public List<File> getInput() {
return input;
}
public File getOutDir() {
return outputDir;
}
public void setOutputDir(File outputDir) {
this.outputDir = outputDir;
}
public boolean isPrintHelp() {
return printHelp;
}
@Override
public int getThreadsCount() {
return threadsCount;
}
@Override
public boolean isCFGOutput() {
return cfgOutput;
}
@Override
public boolean isRawCFGOutput() {
return rawCfgOutput;
}
@Override
public boolean isFallbackMode() {
return fallbackMode;
}
@Override
public boolean isVerbose() {
return verbose;
}
}
+13
View File
@@ -0,0 +1,13 @@
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%-5level - %msg%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="STDOUT"/>
</root>
</configuration>
+9
View File
@@ -0,0 +1,9 @@
ext.jadxClasspath = 'clsp-data/android-4.3.jar'
dependencies {
compile 'com.google.android.tools:dx:1.7'
compile 'ch.qos.logback:logback-classic:1.0.13'
runtime files(jadxClasspath)
}
Binary file not shown.
@@ -0,0 +1,198 @@
package jadx.api;
import jadx.core.Jadx;
import jadx.core.ProcessClass;
import jadx.core.dex.info.ClassInfo;
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.CodegenException;
import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.utils.files.InputFile;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Jadx API usage example:
* <pre><code>
* Decompiler jadx = new Decompiler();
* jadx.loadFile(new File("classes.dex"));
* jadx.setOutputDir(new File("out"));
* jadx.save();
* </code></pre>
* <p/>
* Instead of 'save()' you can get list of decompiled classes:
* <pre><code>
* for(JavaClass cls : jadx.getClasses()) {
* System.out.println(cls.getCode());
* }
* </code></pre>
*/
public final class Decompiler {
private static final Logger LOG = LoggerFactory.getLogger(Decompiler.class);
private final IJadxArgs args;
private final List<InputFile> inputFiles = new ArrayList<InputFile>();
private File outDir;
private RootNode root;
private List<IDexTreeVisitor> passes;
public Decompiler() {
this.args = new DefaultJadxArgs();
init();
}
public Decompiler(IJadxArgs jadxArgs) {
this.args = jadxArgs;
init();
}
public void setOutputDir(File outDir) {
this.outDir = outDir;
init();
}
void init() {
if (outDir == null) {
outDir = new File("jadx-output");
}
this.passes = Jadx.getPassesList(args, outDir);
}
public void loadFile(File file) throws IOException, DecodeException {
loadFiles(Arrays.asList(file));
}
public void loadFiles(List<File> files) throws IOException, DecodeException {
if (files.isEmpty()) {
throw new JadxRuntimeException("Empty file list");
}
inputFiles.clear();
for (File file : files) {
inputFiles.add(new InputFile(file));
}
parse();
}
public void save() {
try {
ExecutorService ex = getSaveExecutor();
ex.awaitTermination(1, TimeUnit.DAYS);
} catch (InterruptedException e) {
LOG.error("Save interrupted", e);
}
}
public ThreadPoolExecutor getSaveExecutor() {
if (root == null) {
throw new JadxRuntimeException("No loaded files");
}
int threadsCount = args.getThreadsCount();
LOG.debug("processing threads count: {}", threadsCount);
ArrayList<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 (ClassNode cls : root.getClasses(false)) {
if (cls.getCode() == null) {
ProcessClass job = new ProcessClass(cls, passList);
executor.execute(job);
} else {
try {
savePass.visit(cls);
} catch (CodegenException e) {
LOG.error("Can't save class {}", cls, e);
}
}
}
executor.shutdown();
return executor;
}
public List<JavaClass> getClasses() {
List<ClassNode> classNodeList = root.getClasses(false);
List<JavaClass> classes = new ArrayList<JavaClass>(classNodeList.size());
for (ClassNode classNode : classNodeList) {
classes.add(new JavaClass(this, classNode));
}
return Collections.unmodifiableList(classes);
}
public List<JavaPackage> getPackages() {
List<JavaClass> classes = getClasses();
Map<String, List<JavaClass>> map = new HashMap<String, List<JavaClass>>();
for (JavaClass javaClass : classes) {
String pkg = javaClass.getPackage();
List<JavaClass> clsList = map.get(pkg);
if (clsList == null) {
clsList = new ArrayList<JavaClass>();
map.put(pkg, clsList);
}
clsList.add(javaClass);
}
List<JavaPackage> packages = new ArrayList<JavaPackage>(map.size());
for (Map.Entry<String, List<JavaClass>> entry : map.entrySet()) {
packages.add(new JavaPackage(entry.getKey(), entry.getValue()));
}
Collections.sort(packages);
for (JavaPackage pkg : packages) {
Collections.sort(pkg.getClasses(), new Comparator<JavaClass>() {
@Override
public int compare(JavaClass o1, JavaClass o2) {
return o1.getShortName().compareTo(o2.getShortName());
}
});
}
return Collections.unmodifiableList(packages);
}
public int getErrorsCount() {
return ErrorsCounter.getErrorCount();
}
void parse() throws DecodeException {
ClassInfo.clearCache();
ErrorsCounter.reset();
root = new RootNode();
LOG.info("loading ...");
root.load(inputFiles);
}
void processClass(ClassNode cls) {
try {
ProcessClass job = new ProcessClass(cls, passes);
LOG.info("processing class {} ...", cls);
job.run();
} catch (Throwable e) {
LOG.error("Process class error", e);
}
}
RootNode getRoot() {
return root;
}
}
@@ -0,0 +1,29 @@
package jadx.api;
public class DefaultJadxArgs implements IJadxArgs {
@Override
public int getThreadsCount() {
return Runtime.getRuntime().availableProcessors();
}
@Override
public boolean isCFGOutput() {
return false;
}
@Override
public boolean isRawCFGOutput() {
return false;
}
@Override
public boolean isFallbackMode() {
return false;
}
@Override
public boolean isVerbose() {
return false;
}
}
@@ -0,0 +1,13 @@
package jadx.api;
public interface IJadxArgs {
int getThreadsCount();
boolean isCFGOutput();
boolean isRawCFGOutput();
boolean isFallbackMode();
boolean isVerbose();
}
@@ -0,0 +1,113 @@
package jadx.api;
import jadx.core.codegen.CodeWriter;
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 java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public final class JavaClass {
private final Decompiler decompiler;
private final ClassNode cls;
private final List<JavaClass> innerClasses;
private final List<JavaField> fields;
private final List<JavaMethod> methods;
JavaClass(Decompiler decompiler, ClassNode classNode) {
this.decompiler = decompiler;
this.cls = classNode;
int inClsCount = cls.getInnerClasses().size();
if (inClsCount == 0) {
this.innerClasses = Collections.emptyList();
} else {
List<JavaClass> list = new ArrayList<JavaClass>(inClsCount);
for (ClassNode inner : cls.getInnerClasses()) {
list.add(new JavaClass(decompiler, inner));
}
this.innerClasses = Collections.unmodifiableList(list);
}
int fieldsCount = cls.getFields().size();
if (fieldsCount == 0) {
this.fields = Collections.emptyList();
} else {
List<JavaField> flds = new ArrayList<JavaField>(fieldsCount);
for (FieldNode f : cls.getFields()) {
flds.add(new JavaField(f));
}
this.fields = Collections.unmodifiableList(flds);
}
int methodsCount = cls.getMethods().size();
if (methodsCount == 0) {
this.methods = Collections.emptyList();
} else {
List<JavaMethod> mths = new ArrayList<JavaMethod>(methodsCount);
for (MethodNode m : cls.getMethods()) {
if (!m.getAccessFlags().isSynthetic()) {
mths.add(new JavaMethod(m));
}
}
Collections.sort(mths, new Comparator<JavaMethod>() {
@Override
public int compare(JavaMethod o1, JavaMethod o2) {
return o1.getName().compareTo(o2.getName());
}
});
this.methods = Collections.unmodifiableList(mths);
}
}
public String getCode() {
CodeWriter code = cls.getCode();
if (code == null) {
decompiler.processClass(cls);
code = cls.getCode();
}
return code != null ? code.toString() : "error processing class";
}
public String getFullName() {
return cls.getFullName();
}
public String getShortName() {
return cls.getShortName();
}
public String getPackage() {
return cls.getPackage();
}
public AccessInfo getAccessInfo() {
return cls.getAccessFlags();
}
public List<JavaClass> getInnerClasses() {
return innerClasses;
}
public List<JavaField> getFields() {
return fields;
}
public List<JavaMethod> getMethods() {
return methods;
}
@Override
public String toString() {
return getFullName();
}
public int getDecompiledLine() {
return cls.getDecompiledLine();
}
}
@@ -0,0 +1,30 @@
package jadx.api;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.FieldNode;
public final class JavaField {
private final FieldNode field;
public JavaField(FieldNode f) {
this.field = f;
}
public String getName() {
return field.getName();
}
public AccessInfo getAccessFlags() {
return field.getAccessFlags();
}
public ArgType getType() {
return field.getType();
}
public int getDecompiledLine() {
return field.getDecompiledLine();
}
}
@@ -0,0 +1,50 @@
package jadx.api;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.MethodNode;
import java.util.List;
public final class JavaMethod {
private final MethodNode mth;
public JavaMethod(MethodNode m) {
this.mth = m;
}
public String getName() {
MethodInfo mi = mth.getMethodInfo();
if (mi.isConstructor()) {
return mth.getParentClass().getShortName();
} else if (mi.isClassInit()) {
return "static";
}
return mi.getName();
}
public AccessInfo getAccessFlags() {
return mth.getAccessFlags();
}
public List<ArgType> getArguments() {
return mth.getMethodInfo().getArgumentsTypes();
}
public ArgType getReturnType() {
return mth.getReturnType();
}
public boolean isConstructor() {
return mth.getMethodInfo().isConstructor();
}
public boolean isClassInit() {
return mth.getMethodInfo().isClassInit();
}
public int getDecompiledLine() {
return mth.getDecompiledLine();
}
}
@@ -0,0 +1,45 @@
package jadx.api;
import java.util.List;
public final class JavaPackage implements Comparable<JavaPackage> {
private final String name;
private final List<JavaClass> classes;
JavaPackage(String name, List<JavaClass> classes) {
this.name = name;
this.classes = classes;
}
public String getName() {
return name;
}
public List<JavaClass> getClasses() {
return classes;
}
@Override
public int compareTo(JavaPackage o) {
return name.compareTo(o.name);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
JavaPackage that = (JavaPackage) o;
if (!name.equals(that.name)) return false;
return true;
}
@Override
public int hashCode() {
return name.hashCode();
}
@Override
public String toString() {
return name;
}
}
@@ -0,0 +1,24 @@
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";
public static final String CLASS_STRING = "java.lang.String";
public static final String CLASS_CLASS = "java.lang.Class";
public static final String CLASS_THROWABLE = "java.lang.Throwable";
public static final String CLASS_ENUM = "java.lang.Enum";
public static final String CLASS_STRING_BUILDER = "java.lang.StringBuilder";
public static final String DALVIK_ANNOTATION_PKG = "dalvik.annotation.";
public static final String DALVIK_SIGNATURE = "dalvik.annotation.Signature";
public static final String DALVIK_INNER_CLASS = "dalvik.annotation.InnerClass";
public static final String DALVIK_THROWS = "dalvik.annotation.Throws";
public static final String DALVIK_ANNOTATION_DEFAULT = "dalvik.annotation.AnnotationDefault";
public static final String DEFAULT_PACKAGE_NAME = "defpackage";
public static final String ANONYMOUS_CLASS_PREFIX = "AnonymousClass_";
}
@@ -0,0 +1,99 @@
package jadx.core;
import jadx.api.IJadxArgs;
import jadx.core.codegen.CodeGen;
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.DotGraphVisitor;
import jadx.core.dex.visitors.EnumVisitor;
import jadx.core.dex.visitors.FallbackModeVisitor;
import jadx.core.dex.visitors.IDexTreeVisitor;
import jadx.core.dex.visitors.MethodInlinerVisitor;
import jadx.core.dex.visitors.ModVisitor;
import jadx.core.dex.visitors.SimplifyVisitor;
import jadx.core.dex.visitors.regions.CheckRegions;
import jadx.core.dex.visitors.regions.CleanRegions;
import jadx.core.dex.visitors.regions.PostRegionVisitor;
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.utils.Utils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.Manifest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Jadx {
private static final Logger LOG = LoggerFactory.getLogger(Jadx.class);
static {
if (Consts.DEBUG)
LOG.info("debug enabled");
if (Jadx.class.desiredAssertionStatus())
LOG.info("assertions enabled");
}
public static List<IDexTreeVisitor> getPassesList(IJadxArgs args, File outDir) {
List<IDexTreeVisitor> passes = new ArrayList<IDexTreeVisitor>();
if (args.isFallbackMode()) {
passes.add(new FallbackModeVisitor());
} else {
passes.add(new BlockMakerVisitor());
passes.add(new TypeResolver());
if (args.isRawCFGOutput())
passes.add(new DotGraphVisitor(outDir, false, true));
passes.add(new ConstInlinerVisitor());
passes.add(new FinishTypeResolver());
passes.add(new ModVisitor());
passes.add(new EnumVisitor());
if (args.isCFGOutput())
passes.add(new DotGraphVisitor(outDir, false));
passes.add(new RegionMakerVisitor());
passes.add(new PostRegionVisitor());
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(new MethodInlinerVisitor());
passes.add(new ClassModifier());
passes.add(new CleanRegions());
}
passes.add(new CodeGen(args));
return passes;
}
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;
}
} catch (IOException e) {
LOG.error("Can't get manifest file", e);
}
return "dev";
}
}
@@ -1,22 +1,22 @@
package jadx;
package jadx.core;
import jadx.dex.nodes.ClassNode;
import jadx.dex.visitors.DepthTraverser;
import jadx.dex.visitors.IDexTreeVisitor;
import jadx.utils.exceptions.DecodeException;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.visitors.DepthTraverser;
import jadx.core.dex.visitors.IDexTreeVisitor;
import jadx.core.utils.exceptions.DecodeException;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
class ProcessClass implements Runnable {
public final class ProcessClass implements Runnable {
private static final Logger LOG = LoggerFactory.getLogger(ProcessClass.class);
private final ClassNode cls;
private final List<IDexTreeVisitor> passes;
ProcessClass(ClassNode cls, List<IDexTreeVisitor> passes) {
public ProcessClass(ClassNode cls, List<IDexTreeVisitor> passes) {
this.cls = cls;
this.passes = passes;
}
@@ -0,0 +1,229 @@
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 java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Classes list for import into classpath graph
*/
public class ClsSet {
private static final Logger LOG = LoggerFactory.getLogger(ClsSet.class);
private static final String CLST_EXTENSION = ".jcst";
private static final String CLST_FILENAME = "core" + CLST_EXTENSION;
private static final String CLST_PKG_PATH = ClsSet.class.getPackage().getName().replace('.', '/');
private static final String JADX_CLS_SET_HEADER = "jadx-cst";
private static final int VERSION = 1;
private static final String STRING_CHARSET = "US-ASCII";
private NClass[] classes;
public void load(RootNode root) {
List<ClassNode> list = root.getClasses(true);
Map<String, NClass> names = new HashMap<String, NClass>(list.size());
int k = 0;
for (ClassNode cls : list) {
String clsRawName = cls.getRawName();
if (cls.getAccessFlags().isPublic()) {
NClass nClass = new NClass(clsRawName, k);
if (names.put(clsRawName, nClass) != null) {
throw new JadxRuntimeException("Duplicate class: " + clsRawName);
}
k++;
} else {
names.put(clsRawName, null);
}
}
classes = new NClass[k];
k = 0;
for (ClassNode cls : list) {
if (cls.getAccessFlags().isPublic()) {
NClass nClass = getCls(cls.getRawName(), names);
nClass.setParents(makeParentsArray(cls, names));
classes[k] = nClass;
k++;
}
}
}
public static NClass[] makeParentsArray(ClassNode cls, Map<String, NClass> names) {
List<NClass> parents = new ArrayList<NClass>(1 + cls.getInterfaces().size());
ClassInfo superClass = cls.getSuperClass();
if (superClass != null) {
NClass c = getCls(superClass.getRawName(), names);
if (c != null) {
parents.add(c);
}
}
for (ClassInfo iface : cls.getInterfaces()) {
NClass c = getCls(iface.getRawName(), names);
if (c != null) {
parents.add(c);
}
}
return parents.toArray(new NClass[parents.size()]);
}
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);
}
return id;
}
void save(File output) throws IOException {
Utils.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();
}
} else {
throw new JadxRuntimeException("Unknown file format: " + outputName);
}
}
public void save(OutputStream output) throws IOException {
DataOutputStream out = new DataOutputStream(output);
out.writeBytes(JADX_CLS_SET_HEADER);
out.writeByte(VERSION);
LOG.info("Classes count: " + classes.length);
out.writeInt(classes.length);
for (NClass cls : classes) {
writeString(out, cls.getName());
}
for (NClass cls : classes) {
NClass[] parents = cls.getParents();
out.writeByte(parents.length);
for (NClass parent : parents) {
out.writeInt(parent.getId());
}
}
}
public void load() throws IOException, DecodeException {
InputStream input = getClass().getResourceAsStream(CLST_FILENAME);
if (input == null) {
throw new JadxRuntimeException("Can't load classpath file: " + CLST_FILENAME);
}
load(input);
}
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);
}
entry = in.getNextEntry();
}
} finally {
in.close();
}
} else {
throw new JadxRuntimeException("Unknown file format: " + name);
}
}
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()];
}
classes[i].setParents(parents);
}
}
private void writeString(DataOutputStream out, String name) throws IOException {
byte[] bytes = name.getBytes(STRING_CHARSET);
out.writeByte(bytes.length);
out.write(bytes);
}
private static String readString(DataInputStream in) throws IOException {
int len = in.readByte();
byte[] bytes = new byte[len];
int count = in.read(bytes);
while (count != len) {
int res = in.read(bytes, count, len - count);
if (res == -1) {
throw new IOException("String read error");
} else {
count += res;
}
}
return new String(bytes, STRING_CHARSET);
}
public int getClassesCount() {
return classes.length;
}
public void addToMap(Map<String, NClass> nameMap) {
for (NClass cls : classes) {
nameMap.put(cls.getName(), cls);
}
}
}
@@ -0,0 +1,113 @@
package jadx.core.clsp;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.exceptions.JadxRuntimeException;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Classes hierarchy graph
*/
public class ClspGraph {
private static final Logger LOG = LoggerFactory.getLogger(ClspGraph.class);
private final Map<String, Set<String>> ancestorCache = new WeakHashMap<String, Set<String>>();
private Map<String, NClass> nameMap;
public void load() throws IOException, DecodeException {
ClsSet set = new ClsSet();
set.load();
addClasspath(set);
}
public void addClasspath(ClsSet set) {
if (nameMap == null) {
nameMap = new HashMap<String, NClass>(set.getClassesCount());
set.addToMap(nameMap);
} else {
throw new JadxRuntimeException("Classpath already loaded");
}
}
public void addApp(List<ClassNode> classes) {
if (nameMap == null) {
throw new JadxRuntimeException("Classpath must be loaded first");
}
int size = classes.size();
NClass[] nClasses = new NClass[size];
for (int i = 0; i < size; i++) {
ClassNode cls = classes.get(i);
NClass nClass = new NClass(cls.getRawName(), -1);
nClasses[i] = nClass;
nameMap.put(cls.getRawName(), nClass);
}
for (int i = 0; i < size; i++) {
nClasses[i].setParents(ClsSet.makeParentsArray(classes.get(i), nameMap));
}
}
public boolean isImplements(String clsName, String implClsName) {
Set<String> anc = getAncestors(clsName);
return anc.contains(implClsName);
}
public String getCommonAncestor(String clsName, String implClsName) {
if (isImplements(clsName, implClsName)) {
return implClsName;
}
Set<String> anc = getAncestors(clsName);
NClass cls = nameMap.get(implClsName);
if (cls != null) {
return searchCommonParent(anc, cls);
} else {
LOG.debug("Missing class: {}", implClsName);
return null;
}
}
private String searchCommonParent(Set<String> anc, NClass cls) {
for (NClass p : cls.getParents()) {
String name = p.getName();
if (anc.contains(name)) {
return name;
} else {
String r = searchCommonParent(anc, p);
if (r != null)
return r;
}
}
return null;
}
private Set<String> getAncestors(String clsName) {
Set<String> result = ancestorCache.get(clsName);
if (result == null) {
result = new HashSet<String>();
ancestorCache.put(clsName, result);
NClass cls = nameMap.get(clsName);
if (cls != null) {
addAncestorsNames(cls, result);
} else {
LOG.debug("Missing class: {}", clsName);
}
}
return result;
}
private void addAncestorsNames(NClass cls, Set<String> result) {
result.add(cls.getName());
for (NClass p : cls.getParents()) {
addAncestorsNames(p, result);
}
}
}
@@ -0,0 +1,67 @@
package jadx.core.clsp;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.files.InputFile;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Utility class for convert dex or jar to jadx classes set (.jcst)
*/
public class ConvertToClsSet {
private static final Logger LOG = LoggerFactory.getLogger(ConvertToClsSet.class);
public static void usage() {
LOG.info("<output .jcst or .jar file> <several input dex or jar files> ");
}
public static void main(String[] args) throws IOException, DecodeException {
if (args.length < 2) {
usage();
System.exit(1);
}
File output = new File(args[0]);
List<InputFile> inputFiles = new ArrayList<InputFile>(args.length - 1);
for (int i = 1; i < args.length; i++) {
File f = new File(args[i]);
if (f.isDirectory()) {
addFilesFromDirectory(f, inputFiles);
} else {
inputFiles.add(new InputFile(f));
}
}
for (InputFile inputFile : inputFiles) {
LOG.info("Loaded: " + inputFile.getFile());
}
RootNode root = new RootNode();
root.load(inputFiles);
ClsSet set = new ClsSet();
set.load(root);
set.save(output);
LOG.info("Output: " + output);
LOG.info("done");
}
private static void addFilesFromDirectory(File dir, List<InputFile> inputFiles) throws IOException, DecodeException {
File[] files = dir.listFiles();
for (File file : files) {
if (file.isDirectory()) {
addFilesFromDirectory(file, inputFiles);
}
if (file.getName().endsWith(".dex")) {
inputFiles.add(new InputFile(file));
}
}
}
}
@@ -0,0 +1,55 @@
package jadx.core.clsp;
/**
* Class node in classpath graph
*/
public class NClass {
private final String name;
private NClass[] parents;
private int id;
public NClass(String name, int id) {
this.name = name;
this.id = id;
}
public String getName() {
return name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public NClass[] getParents() {
return parents;
}
public void setParents(NClass[] parents) {
this.parents = parents;
}
@Override
public int hashCode() {
return name.hashCode();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
NClass nClass = (NClass) o;
if (!name.equals(nClass.name)) return false;
return true;
}
@Override
public String toString() {
return name;
}
}
@@ -1,19 +1,18 @@
package jadx.codegen;
package jadx.core.codegen;
import jadx.Consts;
import jadx.dex.attributes.AttributeType;
import jadx.dex.attributes.IAttributeNode;
import jadx.dex.attributes.annotations.Annotation;
import jadx.dex.attributes.annotations.AnnotationsList;
import jadx.dex.attributes.annotations.MethodParameters;
import jadx.dex.info.FieldInfo;
import jadx.dex.instructions.args.ArgType;
import jadx.dex.nodes.ClassNode;
import jadx.dex.nodes.FieldNode;
import jadx.dex.nodes.MethodNode;
import jadx.utils.StringUtils;
import jadx.utils.Utils;
import jadx.utils.exceptions.JadxRuntimeException;
import jadx.core.Consts;
import jadx.core.dex.attributes.AttributeType;
import jadx.core.dex.attributes.IAttributeNode;
import jadx.core.dex.attributes.annotations.Annotation;
import jadx.core.dex.attributes.annotations.AnnotationsList;
import jadx.core.dex.attributes.annotations.MethodParameters;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.StringUtils;
import jadx.core.utils.exceptions.JadxRuntimeException;
import java.util.Iterator;
import java.util.List;
@@ -60,13 +59,11 @@ public class AnnotationGen {
for (Annotation a : aList.getAll()) {
String aCls = a.getAnnotationClass();
if (aCls.startsWith("dalvik.annotation.")) {
if (aCls.startsWith(Consts.DALVIK_ANNOTATION_PKG)) {
// skip
if (aCls.equals("dalvik.annotation.Signature"))
code.startLine("// signature: "
+ Utils.mergeSignature((List<String>) a.getValues().get("value")));
else if (Consts.DEBUG)
if (Consts.DEBUG) {
code.startLine("// " + a);
}
} else {
code.startLine();
code.add(formatAnnotation(a));
@@ -84,7 +81,7 @@ public class AnnotationGen {
if (vl.size() == 1 && vl.containsKey("value")) {
code.add(encValueToString(vl.get("value")));
} else {
for (Iterator<Entry<String, Object>> it = vl.entrySet().iterator(); it.hasNext();) {
for (Iterator<Entry<String, Object>> it = vl.entrySet().iterator(); it.hasNext(); ) {
Entry<String, Object> e = it.next();
code.add(e.getKey());
code.add(" = ");
@@ -100,15 +97,11 @@ public class AnnotationGen {
@SuppressWarnings("unchecked")
public void addThrows(MethodNode mth, CodeWriter code) {
AnnotationsList anList = (AnnotationsList) mth.getAttributes().get(AttributeType.ANNOTATION_LIST);
if (anList == null || anList.size() == 0)
return;
Annotation an = anList.get("dalvik.annotation.Throws");
Annotation an = mth.getAttributes().getAnnotation(Consts.DALVIK_THROWS);
if (an != null) {
Object exs = an.getValues().get("value");
Object exs = an.getDefaultValue();
code.add(" throws ");
for (Iterator<ArgType> it = ((List<ArgType>) exs).iterator(); it.hasNext();) {
for (Iterator<ArgType> it = ((List<ArgType>) exs).iterator(); it.hasNext(); ) {
ArgType ex = it.next();
code.add(TypeGen.translate(classGen, ex));
if (it.hasNext())
@@ -118,13 +111,9 @@ public class AnnotationGen {
}
public Object getAnnotationDefaultValue(String name) {
AnnotationsList anList = (AnnotationsList) cls.getAttributes().get(AttributeType.ANNOTATION_LIST);
if (anList == null || anList.size() == 0)
return null;
Annotation an = anList.get("dalvik.annotation.AnnotationDefault");
Annotation an = cls.getAttributes().getAnnotation(Consts.DALVIK_ANNOTATION_DEFAULT);
if (an != null) {
Annotation defAnnotation = (Annotation) an.getValues().get("value");
Annotation defAnnotation = (Annotation) an.getDefaultValue();
return defAnnotation.getValues().get(name);
}
return null;
@@ -174,7 +163,7 @@ public class AnnotationGen {
StringBuilder str = new StringBuilder();
str.append('{');
List<Object> list = (List<Object>) val;
for (Iterator<Object> it = list.iterator(); it.hasNext();) {
for (Iterator<Object> it = list.iterator(); it.hasNext(); ) {
Object obj = it.next();
str.append(encValueToString(obj));
if (it.hasNext())
@@ -1,32 +1,43 @@
package jadx.codegen;
package jadx.core.codegen;
import jadx.Consts;
import jadx.dex.attributes.AttributeFlag;
import jadx.dex.attributes.AttributeType;
import jadx.dex.attributes.EnumClassAttr;
import jadx.dex.attributes.EnumClassAttr.EnumField;
import jadx.dex.info.AccessInfo;
import jadx.dex.info.ClassInfo;
import jadx.dex.instructions.args.ArgType;
import jadx.dex.instructions.args.InsnArg;
import jadx.dex.nodes.ClassNode;
import jadx.dex.nodes.FieldNode;
import jadx.dex.nodes.MethodNode;
import jadx.dex.nodes.parser.FieldValueAttr;
import jadx.utils.ErrorsCounter;
import jadx.utils.Utils;
import jadx.utils.exceptions.CodegenException;
import jadx.core.Consts;
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.IAttribute;
import jadx.core.dex.attributes.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.nodes.ClassNode;
import jadx.core.dex.nodes.DexNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.parser.FieldValueAttr;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.CodegenException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.android.dx.rop.code.AccessFlags;
public class ClassGen {
private static final Logger LOG = LoggerFactory.getLogger(ClassGen.class);
private final ClassNode cls;
private final ClassGen parentGen;
private final AnnotationGen annotationGen;
@@ -53,20 +64,22 @@ public class ClassGen {
CodeWriter clsCode = new CodeWriter();
if (!"".equals(cls.getPackage())) {
clsCode.add("package ").add(cls.getPackage()).add(";");
clsCode.endl();
clsCode.add("package ").add(cls.getPackage()).add(';');
clsCode.newLine();
}
if (imports.size() != 0) {
List<String> sortImports = new ArrayList<String>();
for (ClassInfo ic : imports)
int importsCount = imports.size();
if (importsCount != 0) {
List<String> sortImports = new ArrayList<String>(importsCount);
for (ClassInfo ic : imports) {
sortImports.add(ic.getFullName());
}
Collections.sort(sortImports);
for (String imp : sortImports) {
clsCode.startLine("import ").add(imp).add(";");
clsCode.startLine("import ").add(imp).add(';');
}
clsCode.endl();
clsCode.newLine();
sortImports.clear();
imports.clear();
@@ -80,9 +93,12 @@ public class ClassGen {
if (cls.getAttributes().contains(AttributeFlag.DONT_GENERATE))
return;
if (cls.getAttributes().contains(AttributeFlag.INCONSISTENT_CODE))
code.startLine("// jadx: inconsistent code");
makeClassDeclaration(code);
makeClassBody(code);
code.endl();
code.newLine();
}
public void makeClassDeclaration(CodeWriter clsCode) {
@@ -94,6 +110,7 @@ public class ClassGen {
}
annotationGen.addForClass(clsCode);
insertSourceFileInfo(clsCode, cls);
clsCode.startLine(af.makeString());
if (af.isInterface()) {
if (af.isAnnotation())
@@ -105,43 +122,84 @@ public class ClassGen {
clsCode.add("class ");
}
clsCode.add(cls.getShortName());
ClassInfo sup = cls.getSuperClass();
makeGenericMap(clsCode, cls.getGenericMap());
clsCode.add(' ');
ClassInfo sup = cls.getSuperClass();
if (sup != null
&& !sup.getFullName().equals(Consts.CLASS_OBJECT)
&& !sup.getFullName().equals(Consts.CLASS_ENUM)) {
clsCode.add(" extends ").add(useClass(sup));
clsCode.add("extends ").add(useClass(sup)).add(' ');
}
if (cls.getInterfaces().size() > 0 && !af.isAnnotation()) {
if (cls.getAccessFlags().isInterface())
clsCode.add(" extends ");
clsCode.add("extends ");
else
clsCode.add(" implements ");
clsCode.add("implements ");
for (Iterator<ClassInfo> it = cls.getInterfaces().iterator(); it.hasNext();) {
for (Iterator<ClassInfo> it = cls.getInterfaces().iterator(); it.hasNext(); ) {
ClassInfo interf = it.next();
clsCode.add(useClass(interf));
if (it.hasNext())
clsCode.add(", ");
}
if (!cls.getInterfaces().isEmpty())
clsCode.add(' ');
}
clsCode.attachAnnotation(cls);
}
public boolean makeGenericMap(CodeWriter code, Map<ArgType, List<ArgType>> gmap) {
if (gmap == null || gmap.isEmpty())
return false;
code.add('<');
int i = 0;
for (Entry<ArgType, List<ArgType>> e : gmap.entrySet()) {
ArgType type = e.getKey();
List<ArgType> list = e.getValue();
if (i != 0) {
code.add(", ");
}
code.add(useClass(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 (it.hasNext()) {
code.add(" & ");
}
}
}
i++;
}
code.add('>');
return true;
}
public void makeClassBody(CodeWriter clsCode) throws CodegenException {
clsCode.add(" {");
clsCode.add('{');
CodeWriter mthsCode = makeMethods(clsCode, cls.getMethods());
clsCode.add(makeFields(clsCode, cls, cls.getFields()));
CodeWriter fieldsCode = makeFields(clsCode, cls, cls.getFields());
clsCode.add(fieldsCode);
if (fieldsCode.notEmpty() && mthsCode.notEmpty())
clsCode.newLine();
// insert inner classes code
if (cls.getInnerClasses().size() != 0) {
clsCode.add(makeInnerClasses(cls, clsCode.getIndent()));
if (mthsCode.notEmpty())
clsCode.newLine();
}
clsCode.add(mthsCode);
clsCode.startLine("}");
clsCode.startLine('}');
}
private CodeWriter makeInnerClasses(ClassNode cls2, int indent) throws CodegenException {
private CodeWriter makeInnerClasses(ClassNode cls, int indent) throws CodegenException {
CodeWriter innerClsCode = new CodeWriter(indent + 1);
for (ClassNode inCls : cls.getInnerClasses()) {
if (inCls.isAnonymous())
@@ -154,10 +212,13 @@ public class ClassGen {
return innerClsCode;
}
private CodeWriter makeMethods(CodeWriter clsCode, List<MethodNode> mthList) throws CodegenException {
private CodeWriter makeMethods(CodeWriter clsCode, List<MethodNode> mthList) {
CodeWriter code = new CodeWriter(clsCode.getIndent() + 1);
for (Iterator<MethodNode> it = mthList.iterator(); it.hasNext();) {
for (Iterator<MethodNode> it = mthList.iterator(); it.hasNext(); ) {
MethodNode mth = it.next();
if (mth.getAttributes().contains(AttributeFlag.DONT_GENERATE))
continue;
try {
if (mth.getAccessFlags().isAbstract() || mth.getAccessFlags().isNative()) {
MethodGen mthGen = new MethodGen(this, mth);
@@ -169,24 +230,32 @@ public class ClassGen {
code.add(" default ").add(v);
}
}
code.add(";");
code.add(';');
} else {
if (mth.isNoCode())
continue;
MethodGen mthGen = new MethodGen(this, mth);
mthGen.addDefinition(code);
code.add(" {");
if (mth.getAttributes().contains(AttributeFlag.INCONSISTENT_CODE)) {
code.startLine("/* JADX WARNING: inconsistent code */");
LOG.error(ErrorsCounter.formatErrorMsg(mth, " Inconsistent code"));
mthGen.makeMethodDump(code);
}
if (mthGen.addDefinition(code)) {
code.add(' ');
}
code.add('{');
insertSourceFileInfo(code, mth);
code.add(mthGen.makeInstructions(code.getIndent()));
code.startLine("}");
code.startLine('}');
}
} catch (Throwable e) {
String msg = ErrorsCounter.methodError(mth, "Method generation error", e);
code.startLine("/* " + msg + CodeWriter.NL + Utils.getStackTrace(e) + "*/");
code.startLine("/* " + msg + CodeWriter.NL + Utils.getStackTrace(e) + " */");
}
if (it.hasNext())
code.endl();
code.newLine();
}
return code;
}
@@ -196,16 +265,19 @@ public class ClassGen {
EnumClassAttr enumFields = (EnumClassAttr) cls.getAttributes().get(AttributeType.ENUM_CLASS);
if (enumFields != null) {
MethodGen mthGen = new MethodGen(this, enumFields.getStaticMethod());
InsnGen igen = new InsnGen(mthGen, enumFields.getStaticMethod(), false);
for (Iterator<EnumField> it = enumFields.getFields().iterator(); it.hasNext();) {
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();) {
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, enumFields.getStaticMethod(), false);
}
code.add(igen.arg(arg));
if (aIt.hasNext())
code.add(", ");
@@ -222,14 +294,17 @@ public class ClassGen {
code.startLine();
code.add(';');
code.endl();
code.newLine();
}
for (FieldNode f : fields) {
if(f.getAttributes().contains(AttributeFlag.DONT_GENERATE)) {
continue;
}
annotationGen.addForField(code, f);
code.startLine(f.getAccessFlags().makeString());
code.add(TypeGen.translate(this, f.getType()));
code.add(" ");
code.add(' ');
code.add(f.getName());
FieldValueAttr fv = (FieldValueAttr) f.getAttributes().get(AttributeType.FIELD_VALUE);
if (fv != null) {
@@ -240,53 +315,121 @@ public class ClassGen {
code.add(annotationGen.encValueToString(fv.getValue()));
}
}
code.add(";");
code.add(';');
code.attachAnnotation(f);
}
if (fields.size() != 0)
code.endl();
return code;
}
public String useClass(ArgType clsType) {
return useClass(ClassInfo.fromType(cls.dex(), clsType));
if (clsType.isGenericType()) {
return clsType.getObject();
}
return useClass(ClassInfo.fromType(clsType));
}
public String useClass(ClassInfo classInfo) {
if (parentGen != null)
return parentGen.useClass(classInfo);
String baseClass = useClassInternal(cls.getClassInfo(), classInfo);
ArgType[] generics = classInfo.getType().getGenericTypes();
if (generics != null) {
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];
if (gt.isTypeKnown())
sb.append(TypeGen.translate(this, gt));
else
sb.append('?');
}
sb.append('>');
return sb.toString();
} else {
return baseClass;
}
}
private String useClassInternal(ClassInfo useCls, ClassInfo classInfo) {
String clsStr = classInfo.getFullName();
if (fallback)
if (fallback) {
return clsStr;
}
String shortName = classInfo.getShortName();
if (classInfo.getPackage().equals("java.lang") && classInfo.getParentClass() == null) {
return shortName;
} else {
// don't add import if this class inner for current class
if (isInner(classInfo, cls.getClassInfo()))
if (isClassInnerFor(classInfo, useCls)) {
return shortName;
}
// don't add import if this class from same package
if (classInfo.getPackage().equals(useCls.getPackage()) && !classInfo.isInner()) {
return shortName;
}
if (classInfo.getPackage().equals(useCls.getPackage())) {
clsStr = classInfo.getNameWithoutPackage();
}
if (searchCollision(cls.dex(), useCls, shortName)) {
return clsStr;
}
for (ClassInfo cls : imports) {
if (!cls.equals(classInfo)) {
if (cls.getShortName().equals(shortName))
if (cls.getShortName().equals(shortName)) {
return clsStr;
}
}
}
imports.add(classInfo);
addImport(classInfo);
return shortName;
}
}
private boolean isInner(ClassInfo inner, ClassInfo parent) {
private void addImport(ClassInfo classInfo) {
if (parentGen != null) {
parentGen.addImport(classInfo);
} else {
imports.add(classInfo);
}
}
private static boolean isClassInnerFor(ClassInfo inner, ClassInfo parent) {
if (inner.isInner()) {
ClassInfo p = inner.getParentClass();
return p.equals(parent) || isInner(p, parent);
return p.equals(parent) || isClassInnerFor(p, parent);
}
return false;
}
private static boolean searchCollision(DexNode dex, ClassInfo useCls, String shortName) {
if (useCls == null) {
return false;
}
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)) {
return true;
}
}
}
return searchCollision(dex, useCls.getParentClass(), shortName);
}
private void insertSourceFileInfo(CodeWriter code, AttrNode node) {
IAttribute sourceFileAttr = node.getAttributes().get(AttributeType.SOURCE_FILE);
if (sourceFileAttr != null) {
code.startLine("// compiled from: ");
code.add(((SourceFileAttr) sourceFileAttr).getFileName());
}
}
public Set<ClassInfo> getImports() {
return imports;
}
@@ -0,0 +1,29 @@
package jadx.core.codegen;
import jadx.api.IJadxArgs;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.visitors.AbstractVisitor;
import jadx.core.utils.exceptions.CodegenException;
public class CodeGen extends AbstractVisitor {
private final IJadxArgs args;
public CodeGen(IJadxArgs args) {
this.args = args;
}
@Override
public boolean visit(ClassNode cls) throws CodegenException {
ClassGen clsGen = new ClassGen(cls, null, isFallbackMode());
CodeWriter clsCode = clsGen.makeClass();
clsCode.finish();
cls.setCode(clsCode);
return false;
}
public boolean isFallbackMode() {
return args.isFallbackMode();
}
}
@@ -1,9 +1,13 @@
package jadx.codegen;
package jadx.core.codegen;
import jadx.utils.exceptions.JadxRuntimeException;
import jadx.core.dex.attributes.LineAttrNode;
import jadx.core.utils.Utils;
import java.io.File;
import java.io.PrintWriter;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -13,12 +17,15 @@ public class CodeWriter {
private static final int MAX_FILENAME_LENGTH = 128;
public static final String NL = System.getProperty("line.separator");
public static final String INDENT = "\t";
private static final String INDENT = "\t";
private StringBuilder buf = new StringBuilder();
private final StringBuilder buf = new StringBuilder();
private String indentStr;
private int indent;
private int line = 1;
private Map<Object, Integer> annotations = Collections.emptyMap();
public CodeWriter() {
this.indent = 0;
this.indentStr = "";
@@ -29,15 +36,28 @@ public class CodeWriter {
updateIndent();
}
public CodeWriter startLine() {
addLine();
buf.append(indentStr);
return this;
}
public CodeWriter startLine(char c) {
addLine();
buf.append(indentStr);
buf.append(c);
return this;
}
public CodeWriter startLine(String str) {
buf.append(NL);
addLine();
buf.append(indentStr);
buf.append(str);
return this;
}
public CodeWriter startLine(int ind, String str) {
buf.append(NL);
addLine();
buf.append(indentStr);
for (int i = 0; i < ind; i++)
buf.append(INDENT);
@@ -45,12 +65,6 @@ public class CodeWriter {
return this;
}
public CodeWriter startLine() {
buf.append(NL);
buf.append(indentStr);
return this;
}
public CodeWriter add(String str) {
buf.append(str);
return this;
@@ -61,17 +75,47 @@ public class CodeWriter {
return this;
}
public CodeWriter add(CodeWriter mthsCode) {
buf.append(mthsCode.toString());
public CodeWriter add(CodeWriter code) {
line--;
for (Map.Entry<Object, Integer> entry : code.annotations.entrySet()) {
attachAnnotation(entry.getKey(), line + entry.getValue());
}
line += code.line;
buf.append(code.toString());
return this;
}
public CodeWriter endl() {
public CodeWriter newLine() {
addLine();
return this;
}
private void addLine() {
buf.append(NL);
line++;
}
public int getLine() {
return line;
}
public Object attachAnnotation(Object obj) {
return attachAnnotation(obj, line);
}
public Object attachAnnotation(Object obj, int line) {
if (annotations.isEmpty()) {
annotations = new HashMap<Object, Integer>();
}
return annotations.put(obj, line);
}
public CodeWriter indent() {
buf.append(indentStr);
return this;
}
private static final String[] indentCache = new String[] {
private static final String[] INDENT_CACHE = new String[]{
"",
INDENT,
INDENT + INDENT,
@@ -81,11 +125,12 @@ public class CodeWriter {
};
private void updateIndent() {
if (indent < 6) {
this.indentStr = indentCache[indent];
int curIndent = indent;
if (curIndent < 6) {
this.indentStr = INDENT_CACHE[curIndent];
} else {
StringBuilder s = new StringBuilder(indent * INDENT.length());
for (int i = 0; i < indent; i++) {
StringBuilder s = new StringBuilder(curIndent * INDENT.length());
for (int i = 0; i < curIndent; i++) {
s.append(INDENT);
}
this.indentStr = s.toString();
@@ -118,6 +163,18 @@ public class CodeWriter {
updateIndent();
}
public void finish() {
buf.trimToSize();
for (Map.Entry<Object, Integer> entry : annotations.entrySet()) {
Object v = entry.getKey();
if (v instanceof LineAttrNode) {
LineAttrNode l = (LineAttrNode) v;
l.setDecompiledLine(entry.getValue());
}
}
annotations.clear();
}
private static String removeFirstEmptyLine(String str) {
if (str.startsWith(NL)) {
return str.substring(NL.length());
@@ -126,6 +183,10 @@ public class CodeWriter {
}
}
public boolean notEmpty() {
return buf.length() != 0;
}
@Override
public String toString() {
return buf.toString();
@@ -153,7 +214,7 @@ public class CodeWriter {
PrintWriter out = null;
try {
makeDirsForFile(file);
Utils.makeDirsForFile(file);
out = new PrintWriter(file, "UTF-8");
String code = buf.toString();
code = removeFirstEmptyLine(code);
@@ -163,17 +224,6 @@ public class CodeWriter {
} finally {
if (out != null)
out.close();
buf = null;
}
}
private void makeDirsForFile(File file) {
File dir = file.getParentFile();
if (!dir.exists()) {
// if directory already created in other thread mkdirs will return false,
// so check dir existence again
if (!dir.mkdirs() && !dir.exists())
throw new JadxRuntimeException("Can't create directory " + dir);
}
}
@@ -0,0 +1,714 @@
package jadx.core.codegen;
import jadx.core.dex.attributes.AttributeFlag;
import jadx.core.dex.attributes.AttributeType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.attributes.MethodInlineAttr;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.ArithNode;
import jadx.core.dex.instructions.ArithOp;
import jadx.core.dex.instructions.ConstClassNode;
import jadx.core.dex.instructions.ConstStringNode;
import jadx.core.dex.instructions.FillArrayNode;
import jadx.core.dex.instructions.GotoNode;
import jadx.core.dex.instructions.IfNode;
import jadx.core.dex.instructions.IndexInsnNode;
import jadx.core.dex.instructions.InvokeNode;
import jadx.core.dex.instructions.InvokeType;
import jadx.core.dex.instructions.SwitchNode;
import jadx.core.dex.instructions.args.ArgType;
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.RegisterArg;
import jadx.core.dex.instructions.mods.ConstructorInsn;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.InsnUtils;
import jadx.core.utils.RegionUtils;
import jadx.core.utils.StringUtils;
import jadx.core.utils.exceptions.CodegenException;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class InsnGen {
private static final Logger LOG = LoggerFactory.getLogger(InsnGen.class);
protected final MethodGen mgen;
protected final MethodNode mth;
protected final RootNode root;
private final boolean fallback;
private static enum IGState {
SKIP,
NO_SEMICOLON,
NO_RESULT,
BODY_ONLY,
BODY_ONLY_NOWRAP,
}
public InsnGen(MethodGen mgen, MethodNode mth, boolean fallback) {
this.mgen = mgen;
this.mth = mth;
this.root = mth.dex().root();
this.fallback = fallback;
}
private boolean isFallback() {
return fallback;
}
public String arg(InsnNode insn, int arg) throws CodegenException {
return arg(insn.getArg(arg));
}
public String arg(InsnArg arg) throws CodegenException {
return arg(arg, true);
}
public String arg(InsnArg arg, boolean wrap) throws CodegenException {
if (arg.isRegister()) {
return mgen.makeArgName((RegisterArg) arg);
} else if (arg.isLiteral()) {
return lit((LiteralArg) arg);
} else if (arg.isInsnWrap()) {
CodeWriter code = new CodeWriter();
IGState flag = wrap ? IGState.BODY_ONLY : IGState.BODY_ONLY_NOWRAP;
makeInsn(((InsnWrapArg) arg).getWrapInsn(), code, flag);
return code.toString();
} else if (arg.isNamed()) {
return ((NamedArg) arg).getName();
} else if (arg.isField()) {
FieldArg f = (FieldArg) arg;
if (f.isStatic()) {
return sfield(f.getField());
} else {
RegisterArg regArg = new RegisterArg(f.getRegNum());
regArg.replaceTypedVar(f);
return ifield(f.getField(), regArg);
}
} else {
throw new CodegenException("Unknown arg type " + arg);
}
}
public String assignVar(InsnNode insn) throws CodegenException {
RegisterArg arg = insn.getResult();
if (insn.getAttributes().contains(AttributeType.DECLARE_VARIABLE)) {
return declareVar(arg);
} else {
return arg(arg);
}
}
public String declareVar(RegisterArg arg) {
return useType(arg.getType()) + " " + mgen.assignArg(arg);
}
private String lit(LiteralArg arg) {
return TypeGen.literalToString(arg.getLiteral(), arg.getType());
}
private String ifield(FieldInfo field, InsnArg arg) throws CodegenException {
FieldNode fieldNode = mth.getParentClass().searchField(field);
if(fieldNode != null && fieldNode.getAttributes().contains(AttributeFlag.DONT_GENERATE)) {
return "";
}
String name = field.getName();
// TODO: add jadx argument "add this"
// FIXME: check variable names in scope
if (false && arg.isThis()) {
boolean useShort = true;
List<RegisterArg> args = mth.getArguments(false);
for (RegisterArg param : args) {
String paramName = param.getTypedVar().getName();
if (paramName != null && paramName.equals(name)) {
useShort = false;
}
}
if (useShort) {
return name;
}
}
String argStr = arg(arg);
return argStr.isEmpty() ? name : argStr + "." + name;
}
protected String sfield(FieldInfo field) {
String thisClass = mth.getParentClass().getFullName();
ClassInfo declClass = field.getDeclClass();
if (thisClass.startsWith(declClass.getFullName())) {
return field.getName();
}
// Android specific resources class handler
ClassInfo parentClass = declClass.getParentClass();
if (parentClass != null && parentClass.getShortName().equals("R")) {
return useClass(parentClass) + "." + declClass.getShortName() + "." + field.getName();
}
return useClass(declClass) + '.' + field.getName();
}
private void fieldPut(IndexInsnNode insn) {
FieldInfo field = (FieldInfo) insn.getIndex();
String thisClass = mth.getParentClass().getFullName();
if (field.getDeclClass().getFullName().equals(thisClass)) {
// if we generate this field - don't init if its final and used
FieldNode fn = mth.getParentClass().searchField(field);
if (fn != null && fn.getAccessFlags().isFinal())
fn.getAttributes().remove(AttributeType.FIELD_VALUE);
}
}
public String useClass(ClassInfo cls) {
return mgen.getClassGen().useClass(cls);
}
private String useType(ArgType type) {
return TypeGen.translate(mgen.getClassGen(), type);
}
public boolean makeInsn(InsnNode insn, CodeWriter code) throws CodegenException {
return makeInsn(insn, code, null);
}
private boolean makeInsn(InsnNode insn, CodeWriter code, IGState flag) throws CodegenException {
try {
EnumSet<IGState> state = EnumSet.noneOf(IGState.class);
if (flag == IGState.BODY_ONLY || flag == IGState.BODY_ONLY_NOWRAP) {
state.add(flag);
makeInsnBody(code, insn, state);
} else {
CodeWriter body = new CodeWriter(code.getIndent());
makeInsnBody(body, insn, state);
if (state.contains(IGState.SKIP)) {
return false;
}
code.startLine();
if (insn.getSourceLine() != 0) {
code.attachAnnotation(insn.getSourceLine());
}
if (insn.getResult() != null && !state.contains(IGState.NO_RESULT)) {
code.add(assignVar(insn)).add(" = ");
}
code.add(body);
if (!state.contains(IGState.NO_SEMICOLON)) {
code.add(';');
}
}
} catch (Throwable th) {
throw new CodegenException(mth, "Error generate insn: " + insn, th);
}
return true;
}
private void makeInsnBody(CodeWriter code, InsnNode insn, EnumSet<IGState> state) throws CodegenException {
switch (insn.getType()) {
case CONST_STR:
String str = ((ConstStringNode) insn).getString();
code.add(StringUtils.unescapeString(str));
break;
case CONST_CLASS:
ArgType clsType = ((ConstClassNode) insn).getClsType();
code.add(useType(clsType)).add(".class");
break;
case CONST:
LiteralArg arg = (LiteralArg) insn.getArg(0);
code.add(lit(arg));
break;
case MOVE:
code.add(arg(insn.getArg(0), false));
break;
case CHECK_CAST:
case CAST: {
boolean wrap = state.contains(IGState.BODY_ONLY);
if (wrap)
code.add("(");
code.add("(");
code.add(useType(((ArgType) ((IndexInsnNode) insn).getIndex())));
code.add(") ");
code.add(arg(insn.getArg(0)));
if (wrap)
code.add(")");
break;
}
case ARITH:
makeArith((ArithNode) insn, code, state);
break;
case NEG:
String base = "-" + arg(insn.getArg(0));
if (state.contains(IGState.BODY_ONLY)) {
code.add('(').add(base).add(')');
} else {
code.add(base);
}
break;
case RETURN:
if (insn.getArgsCount() != 0)
code.add("return ").add(arg(insn.getArg(0), false));
else
code.add("return");
break;
case BREAK:
code.add("break");
break;
case CONTINUE:
code.add("continue");
break;
case THROW:
code.add("throw ").add(arg(insn.getArg(0), true));
break;
case CMP_L:
case CMP_G:
code.add(String.format("(%1$s > %2$s ? 1 : (%1$s == %2$s ? 0 : -1))", arg(insn, 0), arg(insn, 1)));
break;
case INSTANCE_OF: {
boolean wrap = state.contains(IGState.BODY_ONLY);
if (wrap) {
code.add('(');
}
code.add(arg(insn, 0));
code.add(" instanceof ");
code.add(useType((ArgType) ((IndexInsnNode) insn).getIndex()));
if (wrap) {
code.add(')');
}
break;
}
case CONSTRUCTOR:
makeConstructor((ConstructorInsn) insn, code, state);
break;
case INVOKE:
makeInvoke((InvokeNode) insn, code);
break;
case NEW_ARRAY: {
ArgType arrayType = insn.getResult().getType();
int dim = arrayType.getArrayDimension();
code.add("new ").add(useType(arrayType.getArrayRootElement())).add('[').add(arg(insn, 0)).add(']');
for (int i = 0; i < dim - 1; i++)
code.add("[]");
break;
}
case ARRAY_LENGTH:
code.add(arg(insn, 0)).add(".length");
break;
case FILL_ARRAY:
fillArray((FillArrayNode) insn, code);
break;
case FILLED_NEW_ARRAY:
filledNewArray(insn, code);
break;
case AGET:
code.add(arg(insn.getArg(0))).add('[').add(arg(insn.getArg(1), false)).add(']');
break;
case APUT:
code.add(arg(insn, 0)).add('[').add(arg(insn.getArg(1), false)).add("] = ").add(arg(insn.getArg(2), false));
break;
case IGET: {
FieldInfo fieldInfo = (FieldInfo) ((IndexInsnNode) insn).getIndex();
code.add(ifield(fieldInfo, insn.getArg(0)));
break;
}
case IPUT: {
FieldInfo fieldInfo = (FieldInfo) ((IndexInsnNode) insn).getIndex();
code.add(ifield(fieldInfo, insn.getArg(1))).add(" = ").add(arg(insn.getArg(0), false));
break;
}
case SGET:
code.add(sfield((FieldInfo) ((IndexInsnNode) insn).getIndex()));
break;
case SPUT:
IndexInsnNode node = (IndexInsnNode) insn;
fieldPut(node);
code.add(sfield((FieldInfo) node.getIndex())).add(" = ").add(arg(node.getArg(0), false));
break;
case STR_CONCAT:
StringBuilder sb = new StringBuilder();
for (Iterator<InsnArg> it = insn.getArguments().iterator(); it.hasNext(); ) {
sb.append(arg(it.next()));
if (it.hasNext()) {
sb.append(" + ");
}
}
// TODO: wrap in braces only if necessary
if (state.contains(IGState.BODY_ONLY)) {
code.add('(').add(sb.toString()).add(')');
} else {
code.add(sb.toString());
}
break;
case MONITOR_ENTER:
if (isFallback()) {
code.add("monitor-enter(").add(arg(insn.getArg(0))).add(')');
} else {
state.add(IGState.SKIP);
}
break;
case MONITOR_EXIT:
if (isFallback()) {
code.add("monitor-exit(").add(arg(insn, 0)).add(')');
} else {
state.add(IGState.SKIP);
}
break;
case MOVE_EXCEPTION:
if (isFallback()) {
code.add("move-exception");
} else {
code.add(arg(insn, 0));
}
break;
case TERNARY:
break;
case ARGS:
code.add(arg(insn, 0));
break;
case NOP:
state.add(IGState.SKIP);
break;
/* fallback mode instructions */
case IF:
assert isFallback() : "if insn in not fallback mode";
IfNode ifInsn = (IfNode) insn;
String cond = arg(insn.getArg(0)) + " " + ifInsn.getOp().getSymbol() + " "
+ (ifInsn.isZeroCmp() ? "0" : arg(insn.getArg(1)));
code.add("if (").add(cond).add(") goto ").add(MethodGen.getLabelName(ifInsn.getTarget()));
break;
case GOTO:
assert isFallback();
code.add("goto ").add(MethodGen.getLabelName(((GotoNode) insn).getTarget()));
break;
case SWITCH:
assert isFallback();
SwitchNode sw = (SwitchNode) insn;
code.add("switch(").add(arg(insn, 0)).add(") {");
code.incIndent();
for (int i = 0; i < sw.getCasesCount(); i++) {
code.startLine("case " + sw.getKeys()[i]
+ ": goto " + MethodGen.getLabelName(sw.getTargets()[i]) + ";");
}
code.startLine("default: goto " + MethodGen.getLabelName(sw.getDefaultCaseOffset()) + ";");
code.decIndent();
code.startLine('}');
state.add(IGState.NO_SEMICOLON);
break;
case NEW_INSTANCE:
// only fallback - make new instance in constructor invoke
assert isFallback();
code.add("new " + insn.getResult().getType());
break;
default:
throw new CodegenException(mth, "Unknown instruction: " + insn.getType());
}
}
private void filledNewArray(InsnNode insn, CodeWriter code) throws CodegenException {
int c = insn.getArgsCount();
code.add("new ").add(useType(insn.getResult().getType()));
code.add('{');
for (int i = 0; i < c; i++) {
code.add(arg(insn, i));
if (i + 1 < c)
code.add(", ");
}
code.add('}');
}
private void fillArray(FillArrayNode insn, CodeWriter code) throws CodegenException {
ArgType insnArrayType = insn.getResult().getType();
ArgType insnElementType = insnArrayType.getArrayElement();
ArgType elType = insn.getElementType();
if (!elType.equals(insnElementType) && !insnArrayType.equals(ArgType.OBJECT)) {
ErrorsCounter.methodError(mth,
"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();
}
}
StringBuilder str = new StringBuilder();
Object data = insn.getData();
switch (elType.getPrimitiveType()) {
case BOOLEAN:
case BYTE:
byte[] array = (byte[]) data;
for (byte b : array) {
str.append(TypeGen.literalToString(b, elType));
str.append(", ");
}
break;
case SHORT:
case CHAR:
short[] sarray = (short[]) data;
for (short b : sarray) {
str.append(TypeGen.literalToString(b, elType));
str.append(", ");
}
break;
case INT:
case FLOAT:
int[] iarray = (int[]) data;
for (int b : iarray) {
str.append(TypeGen.literalToString(b, elType));
str.append(", ");
}
break;
case LONG:
case DOUBLE:
long[] larray = (long[]) data;
for (long b : larray) {
str.append(TypeGen.literalToString(b, elType));
str.append(", ");
}
break;
default:
throw new CodegenException(mth, "Unknown type: " + elType);
}
int len = str.length();
str.delete(len - 2, len);
code.add("new ").add(useType(elType)).add("[]{").add(str.toString()).add('}');
}
private void makeConstructor(ConstructorInsn insn, CodeWriter code, EnumSet<IGState> state)
throws CodegenException {
ClassNode cls = mth.dex().resolveClass(insn.getClassType());
if (cls != null && cls.isAnonymous()) {
// anonymous class construction
ClassInfo parent;
if (cls.getInterfaces().size() == 1) {
parent = cls.getInterfaces().get(0);
} else {
parent = cls.getSuperClass();
}
MethodNode defCtr = cls.getDefaultConstructor();
if (RegionUtils.notEmpty(defCtr.getRegion())) {
defCtr.getAttributes().add(AttributeFlag.ANONYMOUS_CONSTRUCTOR);
} else {
defCtr.getAttributes().add(AttributeFlag.DONT_GENERATE);
}
code.add("new ").add(parent == null ? "Object" : useClass(parent)).add("() ");
code.incIndent(2);
new ClassGen(cls, mgen.getClassGen().getParentGen(), fallback).makeClassBody(code);
code.decIndent(2);
return;
}
if (insn.isSelf()) {
// skip
state.add(IGState.SKIP);
return;
}
if (insn.isSuper()) {
code.add("super");
} else if (insn.isThis()) {
code.add("this");
} else {
code.add("new ").add(useClass(insn.getClassType()));
}
generateArguments(code, insn, 0, mth.dex().resolveMethod(insn.getCallMth()));
}
private void makeInvoke(InvokeNode insn, CodeWriter code) throws CodegenException {
MethodInfo callMth = insn.getCallMth();
// inline method
MethodNode callMthNode = mth.dex().resolveMethod(callMth);
if (callMthNode != null
&& callMthNode.getAttributes().contains(AttributeType.METHOD_INLINE)) {
inlineMethod(callMthNode, insn, code);
return;
}
int k = 0;
InvokeType type = insn.getInvokeType();
switch (type) {
case DIRECT:
case VIRTUAL:
case INTERFACE:
InsnArg arg = insn.getArg(0);
// FIXME: add 'this' for equals methods in scope
if (!arg.isThis()) {
String argStr = arg(arg);
if(!argStr.isEmpty()) {
code.add(argStr).add('.');
}
}
k++;
break;
case SUPER:
// use 'super' instead 'this' in 0 arg
code.add("super").add('.');
k++;
break;
case STATIC:
ClassInfo insnCls = mth.getParentClass().getClassInfo();
ClassInfo declClass = callMth.getDeclClass();
if (!insnCls.equals(declClass)) {
code.add(useClass(declClass)).add('.');
}
break;
}
code.add(callMth.getName());
generateArguments(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)) {
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('(');
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(')').add(arg(arg, true));
} else {
code.add(arg(arg, false));
}
if (i < argsCount - 1) {
code.add(", ");
}
origPos++;
}
code.add(')');
} else {
code.add('(');
if (k < argsCount) {
code.add(arg(insn.getArg(k), false));
for (int i = k + 1; i < argsCount; i++) {
code.add(", ");
code.add(arg(insn.getArg(i), false));
}
}
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();
if (callMthNode.getMethodInfo().getArgumentsTypes().isEmpty()) {
makeInsn(inl, code, IGState.BODY_ONLY);
} else {
// remap args
InsnArg[] regs = new InsnArg[callMthNode.getRegsCount()];
List<RegisterArg> callArgs = callMthNode.getArguments(true);
for (int i = 0; i < callArgs.size(); i++) {
InsnArg arg = insn.getArg(i);
RegisterArg callArg = callArgs.get(i);
regs[callArg.getRegNum()] = arg;
}
// replace args
List<RegisterArg> inlArgs = new ArrayList<RegisterArg>();
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);
} else {
InsnArg repl = regs[r.getRegNum()];
if (repl == null) {
LOG.warn("Not passed register {} in method call: {}, {}", r, callMthNode, mth);
} else {
inl.replaceArg(r, repl);
toRevert.put(r, repl);
}
}
}
makeInsn(inl, code, IGState.BODY_ONLY);
// revert changes
for (Entry<RegisterArg, InsnArg> e : toRevert.entrySet()) {
inl.replaceArg(e.getValue(), e.getKey());
}
}
}
private void makeArith(ArithNode insn, CodeWriter code, EnumSet<IGState> state) throws CodegenException {
ArithOp op = insn.getOp();
String v1 = arg(insn.getArg(0));
String v2 = arg(insn.getArg(1));
if (state.contains(IGState.BODY_ONLY)) {
// wrap insn in brackets for save correct operation order
code.add('(').add(v1).add(' ').add(op.getSymbol()).add(' ').add(v2).add(')');
} else if (state.contains(IGState.BODY_ONLY_NOWRAP)) {
code.add(v1).add(' ').add(op.getSymbol()).add(' ').add(v2);
} else {
String res = arg(insn.getResult());
if (res.equals(v1) && insn.getResult().equals(insn.getArg(0))) {
state.add(IGState.NO_RESULT);
// "++" or "--"
if (insn.getArg(1).isLiteral() && (op == ArithOp.ADD || op == ArithOp.SUB)) {
LiteralArg lit = (LiteralArg) insn.getArg(1);
if (lit.isInteger() && lit.getLiteral() == 1) {
code.add(assignVar(insn)).add(op.getSymbol()).add(op.getSymbol());
return;
}
}
// +=, -= ...
v2 = arg(insn.getArg(1), false);
code.add(assignVar(insn)).add(' ').add(op.getSymbol()).add("= ").add(v2);
} else {
code.add(v1).add(' ').add(op.getSymbol()).add(' ').add(v2);
}
}
}
}
@@ -1,31 +1,29 @@
package jadx.codegen;
package jadx.core.codegen;
import jadx.Consts;
import jadx.dex.attributes.AttributeFlag;
import jadx.dex.attributes.AttributeType;
import jadx.dex.attributes.AttributesList;
import jadx.dex.attributes.JadxErrorAttr;
import jadx.dex.attributes.annotations.MethodParameters;
import jadx.dex.info.AccessInfo;
import jadx.dex.instructions.args.ArgType;
import jadx.dex.instructions.args.RegisterArg;
import jadx.dex.nodes.InsnNode;
import jadx.dex.nodes.MethodNode;
import jadx.dex.trycatch.CatchAttr;
import jadx.dex.visitors.DepthTraverser;
import jadx.dex.visitors.FallbackModeVisitor;
import jadx.utils.ErrorsCounter;
import jadx.utils.InsnUtils;
import jadx.utils.Utils;
import jadx.utils.exceptions.CodegenException;
import jadx.utils.exceptions.DecodeException;
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.annotations.MethodParameters;
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.trycatch.CatchAttr;
import jadx.core.dex.visitors.DepthTraverser;
import jadx.core.dex.visitors.FallbackModeVisitor;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.InsnUtils;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.CodegenException;
import jadx.core.utils.exceptions.DecodeException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.slf4j.Logger;
@@ -37,12 +35,12 @@ public class MethodGen {
private static final Logger LOG = LoggerFactory.getLogger(MethodGen.class);
private final MethodNode mth;
private final Set<String> mthArgsDecls;
private final Map<String, ArgType> varDecls = new HashMap<String, ArgType>();
private final ClassGen classGen;
private final boolean fallback;
private final AnnotationGen annotationGen;
private final Set<String> varNames = new HashSet<String>();
public MethodGen(ClassGen classGen, MethodNode mth) {
this.mth = mth;
this.classGen = classGen;
@@ -50,9 +48,8 @@ public class MethodGen {
this.annotationGen = classGen.getAnnotationGen();
List<RegisterArg> args = mth.getArguments(true);
mthArgsDecls = new HashSet<String>(args.size());
for (RegisterArg arg : args) {
mthArgsDecls.add(makeArgName(arg));
varNames.add(makeArgName(arg));
}
}
@@ -60,51 +57,63 @@ public class MethodGen {
return classGen;
}
public void addDefinition(CodeWriter code) {
public boolean addDefinition(CodeWriter code) {
if (mth.getMethodInfo().isClassInit()) {
code.startLine("static");
} else {
if (mth.getAttributes().contains(AttributeFlag.INCONSISTENT_CODE)) {
code.startLine("// FIXME: Jadx generate inconsistent code");
LOG.debug(ErrorsCounter.formatErrorMsg(mth, " Inconsistent code"));
}
annotationGen.addForMethod(code, mth);
AccessInfo ai = mth.getAccessFlags();
// don't add 'abstract' to methods in interface
if (mth.getParentClass().getAccessFlags().isInterface()) {
ai = ai.remove(AccessFlags.ACC_ABSTRACT);
}
code.startLine(ai.makeString());
if (mth.getAccessFlags().isConstructor()) {
code.add(classGen.getClassNode().getShortName()); // constructor
} else {
code.add(TypeGen.translate(classGen, mth.getMethodInfo().getReturnType()));
code.add(" ");
code.add(mth.getName());
}
code.add("(");
mth.resetArgsTypes();
List<RegisterArg> args = mth.getArguments(false);
if (mth.getMethodInfo().isConstructor()
&& mth.getParentClass().getAttributes().contains(AttributeType.ENUM_CLASS)) {
if (args.size() == 2)
args.clear();
else if (args.size() > 2)
args = args.subList(2, args.size());
else
LOG.warn(ErrorsCounter.formatErrorMsg(mth,
"Incorrect number of args for enum constructor: " + args.size()
+ " (expected >= 2)"));
}
code.add(makeArguments(args));
code.add(")");
annotationGen.addThrows(mth, code);
code.attachAnnotation(mth);
return true;
}
if (mth.getAttributes().contains(AttributeFlag.ANONYMOUS_CONSTRUCTOR)) {
// don't add method name and arguments
code.startLine();
code.attachAnnotation(mth);
return false;
}
annotationGen.addForMethod(code, mth);
AccessInfo clsAccFlags = mth.getParentClass().getAccessFlags();
AccessInfo ai = mth.getAccessFlags();
// don't add 'abstract' to methods in interface
if (clsAccFlags.isInterface()) {
ai = ai.remove(AccessFlags.ACC_ABSTRACT);
}
// don't add 'public' for annotations
if (clsAccFlags.isAnnotation()) {
ai = ai.remove(AccessFlags.ACC_PUBLIC);
}
code.startLine(ai.makeString());
if (classGen.makeGenericMap(code, mth.getGenericMap())) {
code.add(' ');
}
if (mth.getAccessFlags().isConstructor()) {
code.add(classGen.getClassNode().getShortName()); // constructor
} else {
code.add(TypeGen.translate(classGen, mth.getReturnType()));
code.add(' ');
code.add(mth.getName());
}
code.add('(');
List<RegisterArg> args = mth.getArguments(false);
if (mth.getMethodInfo().isConstructor()
&& mth.getParentClass().getAttributes().contains(AttributeType.ENUM_CLASS)) {
if (args.size() == 2) {
args.clear();
} else if (args.size() > 2) {
args = args.subList(2, args.size());
} else {
LOG.warn(ErrorsCounter.formatErrorMsg(mth,
"Incorrect number of args for enum constructor: " + args.size()
+ " (expected >= 2)"));
}
}
code.add(makeArguments(args));
code.add(")");
annotationGen.addThrows(mth, code);
code.attachAnnotation(mth);
return true;
}
public CodeWriter makeArguments(List<RegisterArg> args) {
@@ -114,7 +123,7 @@ public class MethodGen {
(MethodParameters) mth.getAttributes().get(AttributeType.ANNOTATION_MTH_PARAMETERS);
int i = 0;
for (Iterator<RegisterArg> it = args.iterator(); it.hasNext();) {
for (Iterator<RegisterArg> it = args.iterator(); it.hasNext(); ) {
RegisterArg arg = it.next();
// add argument annotation
@@ -135,7 +144,7 @@ public class MethodGen {
} else {
argsCode.add(TypeGen.translate(classGen, arg.getType()));
}
argsCode.add(" ");
argsCode.add(' ');
argsCode.add(makeArgName(arg));
i++;
@@ -167,41 +176,50 @@ public class MethodGen {
else
return name;
} else {
return base + "_" + Utils.escape(TypeGen.translate(classGen, arg.getType()));
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
*
* @param arg register variable
* @return variable name
*/
public String assignArg(RegisterArg arg) {
String name = makeArgName(arg);
if (!mthArgsDecls.contains(name))
varDecls.put(name, arg.getType());
if (varNames.add(name) || fallback)
return name;
name = getUniqVarName(name);
arg.getTypedVar().setName(name);
return name;
}
private void makeInitCode(CodeWriter code) throws CodegenException {
InsnGen igen = new InsnGen(this, mth, fallback);
// generate super call
if (mth.getSuperCall() != null)
igen.makeInsn(mth.getSuperCall(), code);
public String assignNamedArg(NamedArg arg) {
String name = arg.getName();
if (varNames.add(name) || fallback)
return name;
name = getUniqVarName(name);
arg.setName(name);
return name;
}
public void makeVariablesDeclaration(CodeWriter code) {
for (Entry<String, ArgType> var : varDecls.entrySet()) {
code.startLine(TypeGen.translate(classGen, var.getValue()));
code.add(" ");
code.add(var.getKey());
code.add(";");
}
if (!varDecls.isEmpty())
code.endl();
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 CodeWriter makeInstructions(int mthIndent) throws CodegenException {
@@ -213,34 +231,19 @@ public class MethodGen {
code.add("\");");
JadxErrorAttr err = (JadxErrorAttr) mth.getAttributes().get(AttributeType.JADX_ERROR);
code.startLine("// FIXME: Jadx error processing method");
code.startLine("// jadx: method processing error");
Throwable cause = err.getCause();
if (cause != null) {
code.endl().add("/*");
code.startLine("Message: ").add(cause.getMessage());
code.newLine();
code.add("/*");
code.startLine("Error: ").add(Utils.getStackTrace(cause));
code.add("*/");
}
// load original instructions
try {
mth.load();
DepthTraverser.visit(new FallbackModeVisitor(), mth);
} catch (DecodeException e) {
// ignore
return code;
}
code.startLine("/*");
makeFullMethodDump(code, mth);
code.startLine("*/");
makeMethodDump(code);
} else {
if (mth.getRegion() != null) {
CodeWriter insns = new CodeWriter(mthIndent + 1);
(new RegionGen(this, mth)).makeRegion(insns, mth.getRegion());
makeInitCode(code);
makeVariablesDeclaration(code);
code.add(insns);
} else {
makeFallbackMethod(code, mth);
@@ -249,7 +252,8 @@ public class MethodGen {
return code;
}
private void makeFullMethodDump(CodeWriter code, MethodNode mth) {
public void makeMethodDump(CodeWriter code) {
code.startLine("/*");
getFallbackMethodGen(mth).addDefinition(code);
code.add(" {");
code.incIndent();
@@ -257,11 +261,23 @@ public class MethodGen {
makeFallbackMethod(code, mth);
code.decIndent();
code.startLine("}");
code.startLine('}');
code.startLine("*/");
}
private void makeFallbackMethod(CodeWriter code, MethodNode mth) {
if (!mth.getAccessFlags().isStatic()) {
if (mth.getInstructions() == null) {
// loadFile original instructions
try {
mth.load();
DepthTraverser.visit(new FallbackModeVisitor(), mth);
} catch (DecodeException e) {
// ignore
code.startLine("Can't loadFile method instructions");
return;
}
}
if (mth.getThisArg() != null) {
code.startLine(getFallbackMethodGen(mth).makeArgName(mth.getThisArg())).add(" = this;");
}
makeFallbackInsns(code, mth, mth.getInstructions(), true);
@@ -280,13 +296,14 @@ public class MethodGen {
}
}
try {
insnGen.makeInsn(insn, code);
if (insnGen.makeInsn(insn, code)) {
CatchAttr catchAttr = (CatchAttr) attrs.get(AttributeType.CATCH_BLOCK);
if (catchAttr != null)
code.add("\t //" + catchAttr);
}
} catch (CodegenException e) {
code.startLine("// error: " + insn);
}
CatchAttr _catch = (CatchAttr) attrs.get(AttributeType.CATCH_BLOCK);
if (_catch != null)
code.add("\t // " + _catch);
}
}
@@ -0,0 +1,325 @@
package jadx.core.codegen;
import jadx.core.dex.attributes.AttributeFlag;
import jadx.core.dex.attributes.AttributeType;
import jadx.core.dex.attributes.DeclareVariableAttr;
import jadx.core.dex.attributes.ForceReturnAttr;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.instructions.ArithNode;
import jadx.core.dex.instructions.IfOp;
import jadx.core.dex.instructions.IndexInsnNode;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.SwitchNode;
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.LiteralArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.IBlock;
import jadx.core.dex.nodes.IContainer;
import jadx.core.dex.nodes.IRegion;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
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.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 java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class RegionGen extends InsnGen {
private static final Logger LOG = LoggerFactory.getLogger(RegionGen.class);
public RegionGen(MethodGen mgen, MethodNode mth) {
super(mgen, mth, false);
}
public void makeRegion(CodeWriter code, IContainer cont) throws CodegenException {
assert cont != null;
if (cont instanceof IBlock) {
makeSimpleBlock((IBlock) cont, code);
} else if (cont instanceof IRegion) {
declareVars(code, cont);
if (cont instanceof Region) {
Region r = (Region) cont;
CatchAttr tc = (CatchAttr) r.getAttributes().get(AttributeType.CATCH_BLOCK);
if (tc != null) {
makeTryCatch(cont, tc.getTryBlock(), code);
} else {
for (IContainer c : r.getSubBlocks())
makeRegion(code, c);
}
} else if (cont instanceof IfRegion) {
code.startLine();
makeIf((IfRegion) cont, code);
} 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 {
throw new CodegenException("Not processed container: " + cont.toString());
}
}
private void declareVars(CodeWriter code, IContainer cont) {
DeclareVariableAttr declVars =
(DeclareVariableAttr) cont.getAttributes().get(AttributeType.DECLARE_VARIABLE);
if (declVars != null) {
for (RegisterArg v : declVars.getVars()) {
code.startLine(declareVar(v)).add(';');
}
}
}
public void makeRegionIndent(CodeWriter code, IContainer region) throws CodegenException {
code.incIndent();
makeRegion(code, region);
code.decIndent();
}
private void makeSimpleBlock(IBlock block, CodeWriter code) throws CodegenException {
for (InsnNode insn : block.getInstructions()) {
makeInsn(insn, code);
}
if (block.getAttributes().contains(AttributeFlag.BREAK)) {
code.startLine("break;");
} else {
IAttribute attr;
if ((attr = block.getAttributes().get(AttributeType.FORCE_RETURN)) != null) {
ForceReturnAttr retAttr = (ForceReturnAttr) attr;
makeInsn(retAttr.getReturnInsn(), code);
}
}
}
private void makeIf(IfRegion region, CodeWriter code) throws CodegenException {
code.add("if (").add(makeCondition(region.getCondition())).add(") {");
makeRegionIndent(code, region.getThenRegion());
code.startLine('}');
IContainer els = region.getElseRegion();
if (els != null && RegionUtils.notEmpty(els)) {
code.add(" else ");
// connect if-else-if block
if (els instanceof Region) {
Region re = (Region) els;
List<IContainer> subBlocks = re.getSubBlocks();
if (subBlocks.size() == 1 && subBlocks.get(0) instanceof IfRegion) {
makeIf((IfRegion) subBlocks.get(0), code);
return;
}
}
code.add('{');
makeRegionIndent(code, els);
code.startLine('}');
}
}
private CodeWriter makeLoop(LoopRegion region, CodeWriter code) throws CodegenException {
BlockNode header = region.getHeader();
if (header != null) {
List<InsnNode> headerInsns = header.getInstructions();
if (headerInsns.size() > 1) {
// write not inlined instructions from header
mth.getAttributes().add(AttributeFlag.INCONSISTENT_CODE);
for (int i = 0; i < headerInsns.size() - 1; i++) {
InsnNode insn = headerInsns.get(i);
makeInsn(insn, code);
}
}
}
IfCondition condition = region.getCondition();
if (condition == null) {
// infinite loop
code.startLine("while (true) {");
makeRegionIndent(code, region.getBody());
code.startLine('}');
return code;
}
if (region.isConditionAtEnd()) {
code.startLine("do {");
makeRegionIndent(code, region.getBody());
code.startLine("} while (").add(makeCondition(condition)).add(");");
} else {
code.startLine("while (").add(makeCondition(condition)).add(") {");
makeRegionIndent(code, region.getBody());
code.startLine('}');
}
return code;
}
private void makeSynchronizedRegion(SynchronizedRegion cont, CodeWriter code) throws CodegenException {
code.startLine("synchronized(").add(arg(cont.getInsn().getArg(0))).add(") {");
makeRegionIndent(code, cont.getRegion());
code.startLine('}');
}
private String makeCondition(IfCondition condition) throws CodegenException {
switch (condition.getMode()) {
case COMPARE:
return makeCompare(condition.getCompare());
case NOT:
return "!" + makeCondition(condition.getArgs().get(0));
case AND:
case OR:
String mode = condition.getMode() == IfCondition.Mode.AND ? " && " : " || ";
StringBuilder sb = new StringBuilder();
for (IfCondition arg : condition.getArgs()) {
if (sb.length() != 0) {
sb.append(mode);
}
String s = makeCondition(arg);
if (arg.isCompare()) {
sb.append(s);
} else {
sb.append('(').append(s).append(')');
}
}
return sb.toString();
default:
return "??" + condition.toString();
}
}
private String makeCompare(IfCondition.Compare compare) throws CodegenException {
IfOp op = compare.getOp();
InsnArg firstArg = compare.getA();
InsnArg secondArg = compare.getB();
if (firstArg.getType().equals(ArgType.BOOLEAN)
&& secondArg.isLiteral()
&& secondArg.getType().equals(ArgType.BOOLEAN)) {
LiteralArg lit = (LiteralArg) secondArg;
if (lit.getLiteral() == 0) {
op = op.invert();
}
if (op == IfOp.EQ) {
return arg(firstArg, false); // == true
} else if (op == IfOp.NE) {
return "!" + arg(firstArg); // != true
}
LOG.warn(ErrorsCounter.formatErrorMsg(mth, "Unsupported boolean condition " + op.getSymbol()));
}
return arg(firstArg, isWrapNeeded(firstArg))
+ " " + op.getSymbol() + " "
+ arg(secondArg, isWrapNeeded(secondArg));
}
private boolean isWrapNeeded(InsnArg arg) {
if (!arg.isInsnWrap()) {
return false;
}
InsnNode insn = ((InsnWrapArg) arg).getWrapInsn();
if (insn.getType() == InsnType.ARITH) {
ArithNode arith = ((ArithNode) insn);
switch (arith.getOp()) {
case ADD:
case SUB:
case MUL:
case DIV:
case REM:
return false;
}
}
return true;
}
private CodeWriter makeSwitch(SwitchRegion sw, CodeWriter code) throws CodegenException {
SwitchNode insn = (SwitchNode) sw.getHeader().getInstructions().get(0);
InsnArg arg = insn.getArg(0);
code.startLine("switch(").add(arg(arg)).add(") {");
int size = sw.getKeys().size();
for (int i = 0; i < size; i++) {
List<Object> keys = sw.getKeys().get(i);
IContainer c = sw.getCases().get(i);
for (Object k : keys) {
code.startLine("case ");
if (k instanceof IndexInsnNode) {
code.add(sfield((FieldInfo) ((IndexInsnNode) k).getIndex()));
}
else {
code.add(TypeGen.literalToString((Integer) k, arg.getType()));
}
code.add(':');
}
makeCaseBlock(c, code);
}
if (sw.getDefaultCase() != null) {
code.startLine("default:");
makeCaseBlock(sw.getDefaultCase(), code);
}
code.startLine('}');
return code;
}
private void makeCaseBlock(IContainer c, CodeWriter code) throws CodegenException {
if (RegionUtils.notEmpty(c)) {
makeRegionIndent(code, c);
if (RegionUtils.hasExitEdge(c)) {
code.startLine(1, "break;");
}
} else {
code.startLine(1, "break;");
}
}
private void makeTryCatch(IContainer region, TryCatchBlock tryCatchBlock, CodeWriter code)
throws CodegenException {
code.startLine("try {");
region.getAttributes().remove(AttributeType.CATCH_BLOCK);
makeRegionIndent(code, region);
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);
allHandler = handler;
}
}
if (allHandler != null) {
makeCatchBlock(code, allHandler);
}
if (tryCatchBlock.getFinalBlock() != null) {
code.startLine("} finally {");
makeRegionIndent(code, tryCatchBlock.getFinalBlock());
}
code.startLine('}');
}
private void makeCatchBlock(CodeWriter code, ExceptionHandler handler)
throws CodegenException {
IContainer region = handler.getHandlerRegion();
if (region != null /* && RegionUtils.notEmpty(region) */) {
code.startLine("} catch (");
code.add(handler.isCatchAll() ? "Throwable" : useClass(handler.getCatchType()));
code.add(' ');
code.add(mgen.assignNamedArg(handler.getArg()));
code.add(") {");
makeRegionIndent(code, region);
}
}
}
@@ -1,10 +1,10 @@
package jadx.codegen;
package jadx.core.codegen;
import jadx.dex.instructions.args.ArgType;
import jadx.dex.instructions.args.PrimitiveType;
import jadx.utils.StringUtils;
import jadx.utils.Utils;
import jadx.utils.exceptions.JadxRuntimeException;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.PrimitiveType;
import jadx.core.utils.StringUtils;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxRuntimeException;
public class TypeGen {
@@ -22,20 +22,6 @@ public class TypeGen {
return stype.getLongName();
}
public static String shortString(ArgType type) {
final PrimitiveType stype = type.getPrimitiveType();
if (stype == null)
return type.toString();
if (stype == PrimitiveType.OBJECT) {
return "L";
}
if (stype == PrimitiveType.ARRAY) {
return shortString(type.getArrayElement()) + "A";
}
return stype.getLongName();
}
public static String signature(ArgType type) {
final PrimitiveType stype = type.getPrimitiveType();
if (stype == PrimitiveType.OBJECT) {
@@ -49,17 +35,17 @@ public class TypeGen {
/**
* Convert literal value to string according to value type
*
* @throws JadxRuntimeException
* for incorrect type or literal value
*
* @throws JadxRuntimeException for incorrect type or literal value
*/
public static String literalToString(long lit, ArgType type) {
if (type == null || !type.isTypeKnown()) {
String n = Long.toString(lit);
if (Math.abs(lit) > 100)
if (Math.abs(lit) > 100) {
n += "; // 0x" + Long.toHexString(lit)
+ " float:" + Float.intBitsToFloat((int) lit)
+ " double:" + Double.longBitsToDouble(lit);
}
return n;
}
@@ -120,9 +106,9 @@ public class TypeGen {
}
private static String wrapNegNum(boolean lz, String str) {
if (lz)
return "(" + str + ")";
else
// if (lz)
// return "(" + str + ")";
// else
return str;
}
}
@@ -1,4 +1,4 @@
package jadx.deobf;
package jadx.core.deobf;
import java.util.Arrays;
import java.util.HashSet;
@@ -6,8 +6,8 @@ import java.util.Set;
public class NameMapper {
private static final Set<String> reservedNames = new HashSet<String>(
Arrays.asList(new String[] {
private static final Set<String> RESERVED_NAMES = new HashSet<String>(
Arrays.asList(new String[]{
"abstract",
"assert",
"boolean",
@@ -21,7 +21,7 @@ public class NameMapper {
"continue",
"default",
"do",
"double ",
"double",
"else",
"enum",
"extends",
@@ -64,7 +64,7 @@ public class NameMapper {
}));
public static boolean isReserved(String str) {
return reservedNames.contains(str);
return RESERVED_NAMES.contains(str);
}
}
@@ -1,4 +1,4 @@
package jadx.dex.attributes;
package jadx.core.dex.attributes;
public abstract class AttrNode implements IAttributeNode {
@@ -1,4 +1,4 @@
package jadx.dex.attributes;
package jadx.core.dex.attributes;
public enum AttributeFlag {
TRY_ENTER,
@@ -14,6 +14,10 @@ public enum AttributeFlag {
DONT_SHRINK,
DONT_GENERATE,
SKIP,
SKIP_FIRST_ARG,
ANONYMOUS_CONSTRUCTOR,
INCONSISTENT_CODE, // warning about incorrect decompilation
}
@@ -0,0 +1,62 @@
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),
ENUM_CLASS(true),
ANNOTATION_LIST(true),
ANNOTATION_MTH_PARAMETERS(true),
SOURCE_FILE(true),
DECLARE_VARIABLE(true);
private static final int NOT_UNIQ_COUNT;
private final boolean uniq;
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;
}
private AttributeType(boolean isUniq) {
this.uniq = isUniq;
}
public boolean isUniq() {
return uniq;
}
public boolean notUniq() {
return !uniq;
}
}
@@ -1,6 +1,8 @@
package jadx.dex.attributes;
package jadx.core.dex.attributes;
import jadx.utils.Utils;
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;
@@ -12,7 +14,13 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
public class AttributesList {
/**
* 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;
@@ -22,16 +30,11 @@ public class AttributesList {
public AttributesList() {
flags = EnumSet.noneOf(AttributeFlag.class);
uniqAttr = new EnumMap<AttributeType, IAttribute>(AttributeType.class);
attributes = new ArrayList<IAttribute>(1);
attrCount = new int[AttributeType.values().length];
attributes = new ArrayList<IAttribute>(0);
attrCount = new int[AttributeType.getNotUniqCount()];
}
public void add(IAttribute attr) {
if (attr.getType().isUniq())
uniqAttr.put(attr.getType(), attr);
else
addMultiAttribute(attr);
}
// Flags
public void add(AttributeFlag flag) {
flags.add(flag);
@@ -45,12 +48,21 @@ public class AttributesList {
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 getCountInternal(AttributeType type) {
private int getMultiCountInternal(AttributeType type) {
return attrCount[type.ordinal()];
}
@@ -65,15 +77,14 @@ public class AttributesList {
if (type.isUniq())
return uniqAttr.containsKey(type);
else
return getCountInternal(type) != 0;
return getMultiCountInternal(type) != 0;
}
public IAttribute get(AttributeType type) {
if (type.isUniq()) {
return uniqAttr.get(type);
} else {
int count = getCountInternal(type);
if (count != 0) {
if (getMultiCountInternal(type) != 0) {
for (IAttribute attr : attributes)
if (attr.getType() == type)
return attr;
@@ -84,16 +95,24 @@ public class AttributesList {
public int getCount(AttributeType type) {
if (type.isUniq()) {
return 0;
return uniqAttr.containsKey(type) ? 1 : 0;
} else {
return getCountInternal(type);
return getMultiCountInternal(type);
}
}
public Annotation getAnnotation(String cls) {
AnnotationsList aList = (AnnotationsList) get(AttributeType.ANNOTATION_LIST);
if (aList == null || aList.size() == 0)
return null;
return aList.get(cls);
}
public List<IAttribute> getAll(AttributeType type) {
assert type.notUniq();
int count = getCountInternal(type);
int count = getMultiCountInternal(type);
if (count == 0) {
return Collections.emptyList();
} else {
@@ -110,7 +129,7 @@ public class AttributesList {
if (type.isUniq()) {
uniqAttr.remove(type);
} else {
for (Iterator<IAttribute> it = attributes.iterator(); it.hasNext();) {
for (Iterator<IAttribute> it = attributes.iterator(); it.hasNext(); ) {
IAttribute attr = it.next();
if (attr.getType() == type)
it.remove();
@@ -119,6 +138,26 @@ public class AttributesList {
}
}
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();
@@ -1,10 +1,10 @@
package jadx.dex.attributes;
package jadx.core.dex.attributes;
import jadx.dex.instructions.args.RegisterArg;
import jadx.dex.instructions.args.TypedVar;
import jadx.dex.nodes.MethodNode;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.args.TypedVar;
import jadx.core.dex.nodes.MethodNode;
public class BlockRegState {
public final class BlockRegState {
private final RegisterArg[] regs;
@@ -21,19 +21,19 @@ public class BlockRegState {
}
public void assignReg(RegisterArg arg) {
int rn = arg.getRegNum();
regs[rn] = new RegisterArg(rn, arg.getType());
use(arg);
regs[arg.getRegNum()] = arg;
arg.getTypedVar().getUseList().add(arg);
}
public void use(RegisterArg arg) {
TypedVar regType = regs[arg.getRegNum()].getTypedVar();
RegisterArg reg = regs[arg.getRegNum()];
TypedVar regType = reg.getTypedVar();
if (regType == null) {
regType = new TypedVar(arg.getType());
regs[arg.getRegNum()].setTypedVar(regType);
reg.forceSetTypedVar(regType);
}
arg.replace(regType);
regType.getUseList().add(arg);
arg.replaceTypedVar(reg);
reg.getTypedVar().getUseList().add(arg);
}
public RegisterArg getRegister(int r) {
@@ -1,7 +1,7 @@
package jadx.dex.attributes;
package jadx.core.dex.attributes;
import jadx.dex.instructions.args.RegisterArg;
import jadx.utils.Utils;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.utils.Utils;
import java.util.List;
@@ -1,9 +1,9 @@
package jadx.dex.attributes;
package jadx.core.dex.attributes;
import jadx.dex.instructions.args.InsnArg;
import jadx.dex.nodes.ClassNode;
import jadx.dex.nodes.MethodNode;
import jadx.utils.Utils;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.Utils;
import java.util.ArrayList;
import java.util.Collections;
@@ -1,7 +1,7 @@
package jadx.dex.attributes;
package jadx.core.dex.attributes;
import jadx.dex.nodes.InsnNode;
import jadx.utils.Utils;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.utils.Utils;
public class ForceReturnAttr implements IAttribute {
@@ -1,4 +1,4 @@
package jadx.dex.attributes;
package jadx.core.dex.attributes;
public interface IAttribute {
@@ -1,4 +1,4 @@
package jadx.dex.attributes;
package jadx.core.dex.attributes;
public interface IAttributeNode {
@@ -1,6 +1,6 @@
package jadx.dex.attributes;
package jadx.core.dex.attributes;
import jadx.utils.Utils;
import jadx.core.utils.Utils;
public class JadxErrorAttr implements IAttribute {
@@ -1,6 +1,6 @@
package jadx.dex.attributes;
package jadx.core.dex.attributes;
import jadx.utils.InsnUtils;
import jadx.core.utils.InsnUtils;
public class JumpAttribute implements IAttribute {
@@ -0,0 +1,24 @@
package jadx.core.dex.attributes;
public abstract class LineAttrNode extends AttrNode {
private int sourceLine;
private int decompiledLine;
public int getSourceLine() {
return sourceLine;
}
public void setSourceLine(int sourceLine) {
this.sourceLine = sourceLine;
}
public int getDecompiledLine() {
return decompiledLine;
}
public void setDecompiledLine(int decompiledLine) {
this.decompiledLine = decompiledLine;
}
}
@@ -1,7 +1,7 @@
package jadx.dex.attributes;
package jadx.core.dex.attributes;
import jadx.dex.nodes.BlockNode;
import jadx.utils.BlockUtils;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.utils.BlockUtils;
import java.util.Collections;
import java.util.HashSet;
@@ -0,0 +1,26 @@
package jadx.core.dex.attributes;
import jadx.core.dex.nodes.InsnNode;
public class MethodInlineAttr implements IAttribute {
private final InsnNode insn;
public MethodInlineAttr(InsnNode insn) {
this.insn = insn;
}
public InsnNode getInsn() {
return insn;
}
@Override
public AttributeType getType() {
return AttributeType.METHOD_INLINE;
}
@Override
public String toString() {
return "INLINE: " + insn;
}
}
@@ -0,0 +1,24 @@
package jadx.core.dex.attributes;
public class SourceFileAttr implements IAttribute {
private final String fileName;
public SourceFileAttr(String fileName) {
this.fileName = fileName;
}
public String getFileName() {
return fileName;
}
@Override
public AttributeType getType() {
return AttributeType.SOURCE_FILE;
}
@Override
public String toString() {
return "SOURCE:" + fileName;
}
}
@@ -1,6 +1,6 @@
package jadx.dex.attributes.annotations;
package jadx.core.dex.attributes.annotations;
import jadx.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.ArgType;
import java.util.Map;
@@ -36,6 +36,10 @@ public class Annotation {
return values;
}
public Object getDefaultValue() {
return values.get("value");
}
@Override
public String toString() {
return "Annotation[" + visibility + ", " + atype + ", " + values + "]";
@@ -1,8 +1,8 @@
package jadx.dex.attributes.annotations;
package jadx.core.dex.attributes.annotations;
import jadx.dex.attributes.AttributeType;
import jadx.dex.attributes.IAttribute;
import jadx.utils.Utils;
import jadx.core.dex.attributes.AttributeType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.utils.Utils;
import java.util.Collection;
import java.util.HashMap;
@@ -1,8 +1,8 @@
package jadx.dex.attributes.annotations;
package jadx.core.dex.attributes.annotations;
import jadx.dex.attributes.AttributeType;
import jadx.dex.attributes.IAttribute;
import jadx.utils.Utils;
import jadx.core.dex.attributes.AttributeType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.utils.Utils;
import java.util.ArrayList;
import java.util.List;
@@ -1,6 +1,6 @@
package jadx.dex.info;
package jadx.core.dex.info;
import jadx.Consts;
import jadx.core.Consts;
import com.android.dx.rop.code.AccessFlags;
@@ -37,6 +37,18 @@ public class AccessInfo {
return new AccessInfo(f, type);
}
public boolean isPublic() {
return (accFlags & AccessFlags.ACC_PUBLIC) != 0;
}
public boolean isProtected() {
return (accFlags & AccessFlags.ACC_PROTECTED) != 0;
}
public boolean isPrivate() {
return (accFlags & AccessFlags.ACC_PRIVATE) != 0;
}
public boolean isAbstract() {
return (accFlags & AccessFlags.ACC_ABSTRACT) != 0;
}
@@ -81,19 +93,31 @@ public class AccessInfo {
return (accFlags & AccessFlags.ACC_VARARGS) != 0;
}
public int getFlags() {
return accFlags;
public boolean isSynchronized() {
return (accFlags & (AccessFlags.ACC_SYNCHRONIZED | AccessFlags.ACC_DECLARED_SYNCHRONIZED)) != 0;
}
public boolean isTransient() {
return (accFlags & AccessFlags.ACC_TRANSIENT) != 0;
}
public boolean isVolatile() {
return (accFlags & AccessFlags.ACC_VOLATILE) != 0;
}
public AFType getType() {
return type;
}
public String makeString() {
StringBuilder code = new StringBuilder();
if ((accFlags & AccessFlags.ACC_PUBLIC) != 0)
if (isPublic())
code.append("public ");
if ((accFlags & AccessFlags.ACC_PRIVATE) != 0)
if (isPrivate())
code.append("private ");
if ((accFlags & AccessFlags.ACC_PROTECTED) != 0)
if (isProtected())
code.append("protected ");
if (isStatic())
@@ -110,10 +134,7 @@ public class AccessInfo {
switch (type) {
case METHOD:
if ((accFlags & AccessFlags.ACC_SYNCHRONIZED) != 0)
code.append("synchronized ");
if ((accFlags & AccessFlags.ACC_DECLARED_SYNCHRONIZED) != 0)
if (isSynchronized())
code.append("synchronized ");
if (isBridge())
@@ -126,10 +147,10 @@ public class AccessInfo {
break;
case FIELD:
if ((accFlags & AccessFlags.ACC_VOLATILE) != 0)
if (isVolatile())
code.append("volatile ");
if ((accFlags & AccessFlags.ACC_TRANSIENT) != 0)
if (isTransient())
code.append("transient ");
break;
@@ -153,4 +174,21 @@ public class AccessInfo {
return code.toString();
}
public String rawString() {
switch (type){
case CLASS:
return AccessFlags.classString(accFlags);
case FIELD:
return AccessFlags.fieldString(accFlags);
case METHOD:
return AccessFlags.methodString(accFlags);
default:
return "?";
}
}
@Override
public String toString() {
return "AccessInfo: " + type + " 0x" + Integer.toHexString(accFlags) + " (" + rawString() + ")";
}
}
@@ -1,24 +1,17 @@
package jadx.dex.info;
package jadx.core.dex.info;
import jadx.deobf.NameMapper;
import jadx.dex.instructions.args.ArgType;
import jadx.dex.nodes.DexNode;
import jadx.core.Consts;
import jadx.core.deobf.NameMapper;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.DexNode;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
import java.util.WeakHashMap;
public final class ClassInfo {
private static final Map<ArgType, ClassInfo> CLASSINFO_CACHE = new HashMap<ArgType, ClassInfo>();
private static final String DEFAULT_PACKAGE_NAME = "defpackage";
private final String clsName;
private final String clsPackage;
private final ArgType type;
private final String fullName;
private final ClassInfo parentClass; // not equals null if this is inner class
private static final Map<ArgType, ClassInfo> CLASSINFO_CACHE = new WeakHashMap<ArgType, ClassInfo>();
public static ClassInfo fromDex(DexNode dex, int clsIndex) {
if (clsIndex == DexNode.NO_INDEX)
@@ -28,17 +21,17 @@ public final class ClassInfo {
if (type.isArray())
type = ArgType.OBJECT;
return fromType(dex, type);
return fromType(type);
}
public static ClassInfo fromName(DexNode dex, String clsName) {
return fromType(dex, ArgType.object(clsName));
public static ClassInfo fromName(String clsName) {
return fromType(ArgType.object(clsName));
}
public static ClassInfo fromType(DexNode dex, ArgType type) {
public static ClassInfo fromType(ArgType type) {
ClassInfo cls = CLASSINFO_CACHE.get(type);
if (cls == null) {
cls = new ClassInfo(dex, type);
cls = new ClassInfo(type);
CLASSINFO_CACHE.put(type, cls);
}
return cls;
@@ -48,20 +41,28 @@ public final class ClassInfo {
CLASSINFO_CACHE.clear();
}
private ClassInfo(DexNode dex, ArgType type) {
private final ArgType type;
private String pkg;
private String name;
private String fullName;
private ClassInfo parentClass; // not equals null if this is inner class
private ClassInfo(ArgType type) {
assert type.isObject() : "Not class type: " + type;
this.type = type;
splitNames(true);
}
private void splitNames(boolean canBeInner) {
String fullObjectName = type.getObject();
assert fullObjectName.indexOf('/') == -1;
assert fullObjectName.indexOf('/') == -1 : "Raw type: " + type;
boolean notObfuscated = dex.root().getJadxArgs().isNotObfuscated();
String name;
String pkg;
int dot = fullObjectName.lastIndexOf('.');
if (dot == -1) {
// rename default package if it used from class with package (often for obfuscated apps),
pkg = (notObfuscated ? "" : DEFAULT_PACKAGE_NAME);
pkg = Consts.DEFAULT_PACKAGE_NAME;
name = fullObjectName;
} else {
pkg = fullObjectName.substring(0, dot);
@@ -69,31 +70,30 @@ public final class ClassInfo {
}
int sep = name.lastIndexOf('$');
if (sep > 0 && sep != name.length() - 1) {
if (canBeInner && sep > 0 && sep != name.length() - 1) {
String parClsName = pkg + '.' + name.substring(0, sep);
if (notObfuscated || dex.root().isClassExists(parClsName)) {
parentClass = fromName(dex, parClsName);
name = name.substring(sep + 1);
} else {
parentClass = null;
}
parentClass = fromName(parClsName);
name = name.substring(sep + 1);
} else {
parentClass = null;
}
if (Character.isDigit(name.charAt(0)))
name = "InnerClass_" + name;
if (NameMapper.isReserved(name))
char firstChar = name.charAt(0);
if (Character.isDigit(firstChar)) {
name = Consts.ANONYMOUS_CLASS_PREFIX + name;
} else if (firstChar == '$') {
name = "_" + name;
}
if (NameMapper.isReserved(name)) {
name += "_";
}
this.fullName = (parentClass != null ? parentClass.getFullName() : pkg) + "." + name;
this.clsName = name;
this.clsPackage = pkg;
this.name = name;
}
public String getFullPath() {
return clsPackage.replace('.', File.separatorChar) + File.separatorChar
return pkg.replace('.', File.separatorChar)
+ File.separatorChar
+ getNameWithoutPackage().replace('.', '_');
}
@@ -101,20 +101,28 @@ public final class ClassInfo {
return fullName;
}
public boolean isObject() {
return fullName.equals(Consts.CLASS_OBJECT);
}
public String getShortName() {
return clsName;
return name;
}
public String getRawName() {
return type.getObject();
}
public String getPackage() {
return clsPackage;
return pkg;
}
public boolean isPackageDefault() {
return clsPackage.isEmpty() || clsPackage.equals(DEFAULT_PACKAGE_NAME);
return pkg.isEmpty() || pkg.equals(Consts.DEFAULT_PACKAGE_NAME);
}
public String getNameWithoutPackage() {
return (parentClass != null ? parentClass.getNameWithoutPackage() + "." : "") + clsName;
return (parentClass != null ? parentClass.getNameWithoutPackage() + "." : "") + name;
}
public ClassInfo getParentClass() {
@@ -125,18 +133,22 @@ public final class ClassInfo {
return parentClass != null;
}
public void notInner() {
splitNames(false);
}
public ArgType getType() {
return type;
}
@Override
public String toString() {
return getFullName();
return fullName;
}
@Override
public int hashCode() {
return this.getFullName().hashCode();
return fullName.hashCode();
}
@Override
@@ -1,12 +1,11 @@
package jadx.dex.info;
package jadx.core.dex.info;
import jadx.dex.attributes.AttrNode;
import jadx.dex.instructions.args.ArgType;
import jadx.dex.nodes.DexNode;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.DexNode;
import com.android.dx.io.FieldId;
public class FieldInfo extends AttrNode {
public class FieldInfo {
private final String name;
private final ArgType type;
@@ -17,7 +16,7 @@ public class FieldInfo extends AttrNode {
return new FieldInfo(dex, index);
}
protected FieldInfo(DexNode dex, int ind) {
private FieldInfo(DexNode dex, int ind) {
FieldId field = dex.getFieldId(ind);
this.name = dex.getString(field.getNameIndex());
this.type = dex.getType(field.getTypeIndex());
@@ -40,6 +39,26 @@ public class FieldInfo extends AttrNode {
return declClass;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
FieldInfo fieldInfo = (FieldInfo) o;
if (!name.equals(fieldInfo.name)) return false;
if (!type.equals(fieldInfo.type)) return false;
if (!declClass.equals(fieldInfo.declClass)) return false;
return true;
}
@Override
public int hashCode() {
int result = name.hashCode();
result = 31 * result + type.hashCode();
result = 31 * result + declClass.hashCode();
return result;
}
@Override
public String toString() {
return declClass + "." + name + " " + type;
@@ -1,9 +1,9 @@
package jadx.dex.info;
package jadx.core.dex.info;
import jadx.codegen.TypeGen;
import jadx.dex.instructions.args.ArgType;
import jadx.dex.nodes.DexNode;
import jadx.utils.Utils;
import jadx.core.codegen.TypeGen;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.DexNode;
import jadx.core.utils.Utils;
import java.util.List;
@@ -49,6 +49,10 @@ public final class MethodInfo {
return declClass.getFullName() + "." + name;
}
public String getFullId() {
return declClass.getFullName() + "." + shortId;
}
/**
* Method name and signature
*/
@@ -68,6 +72,10 @@ public final class MethodInfo {
return args;
}
public int getArgsCount() {
return args.size();
}
public boolean isConstructor() {
return name.equals("<init>");
}
@@ -76,6 +84,28 @@ public final class MethodInfo {
return name.equals("<clinit>");
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + declClass.hashCode();
result = prime * result + retType.hashCode();
result = prime * result + shortId.hashCode();
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
MethodInfo other = (MethodInfo) obj;
if (!shortId.equals(other.shortId)) return false;
if (!retType.equals(other.retType)) return false;
if (!declClass.equals(other.declClass)) return false;
return true;
}
@Override
public String toString() {
return retType + " " + declClass.getFullName() + "." + name
@@ -1,11 +1,10 @@
package jadx.dex.instructions;
package jadx.core.dex.instructions;
import jadx.dex.instructions.args.ArgType;
import jadx.dex.instructions.args.InsnArg;
import jadx.dex.instructions.args.RegisterArg;
import jadx.dex.nodes.InsnNode;
import jadx.dex.nodes.MethodNode;
import jadx.utils.InsnUtils;
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.InsnUtils;
import com.android.dx.io.instructions.DecodedInstruction;
@@ -13,9 +12,8 @@ public class ArithNode extends InsnNode {
private final ArithOp op;
public ArithNode(MethodNode mth, DecodedInstruction insn, ArithOp op, ArgType type,
boolean literal) {
super(mth, InsnType.ARITH, 2);
public ArithNode(DecodedInstruction insn, ArithOp op, ArgType type, boolean literal) {
super(InsnType.ARITH, 2);
this.op = op;
setResult(InsnArg.reg(insn, 0, type));
@@ -44,19 +42,19 @@ public class ArithNode extends InsnNode {
assert getArgsCount() == 2;
}
public ArithNode(MethodNode mth, ArithOp op, RegisterArg res, InsnArg a, InsnArg b) {
super(mth, InsnType.ARITH, 2);
public ArithNode(ArithOp op, RegisterArg res, InsnArg a, InsnArg b) {
super(InsnType.ARITH, 2);
this.op = op;
setResult(res);
addArg(a);
addArg(b);
this.op = op;
}
public ArithNode(MethodNode mth, ArithOp op, RegisterArg res, InsnArg a) {
super(mth, InsnType.ARITH, 1);
public ArithNode(ArithOp op, RegisterArg res, InsnArg a) {
super(InsnType.ARITH, 1);
this.op = op;
setResult(res);
addArg(a);
this.op = op;
}
public ArithOp getOp() {
@@ -68,7 +66,9 @@ public class ArithNode extends InsnNode {
return InsnUtils.formatOffset(offset) + ": "
+ InsnUtils.insnTypeToString(insnType)
+ getResult() + " = "
+ getArg(0) + " " + op.getSymbol() + " " + getArg(1);
+ getArg(0) + " "
+ op.getSymbol() + " "
+ (getArgsCount() == 2 ? getArg(1) : "");
}
}
@@ -1,4 +1,4 @@
package jadx.dex.instructions;
package jadx.core.dex.instructions;
public enum ArithOp {
ADD("+"),
@@ -7,9 +7,6 @@ public enum ArithOp {
DIV("/"),
REM("%"),
INC("++"),
DEC("--"),
AND("&"),
OR("|"),
XOR("^"),
@@ -0,0 +1,23 @@
package jadx.core.dex.instructions;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.InsnNode;
public class ConstClassNode extends InsnNode {
private final ArgType clsType;
public ConstClassNode(ArgType clsType) {
super(InsnType.CONST_CLASS, 0);
this.clsType = clsType;
}
public ArgType getClsType() {
return clsType;
}
@Override
public String toString() {
return super.toString() + " " + clsType;
}
}
@@ -0,0 +1,22 @@
package jadx.core.dex.instructions;
import jadx.core.dex.nodes.InsnNode;
public class ConstStringNode extends InsnNode {
private final String str;
public ConstStringNode(String str) {
super(InsnType.CONST_STR, 0);
this.str = str;
}
public String getString() {
return str;
}
@Override
public String toString() {
return super.toString() + " \"" + str + "\"";
}
}
@@ -0,0 +1,56 @@
package jadx.core.dex.instructions;
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.InsnNode;
import jadx.core.utils.exceptions.JadxRuntimeException;
import com.android.dx.io.instructions.FillArrayDataPayloadDecodedInstruction;
public class FillArrayNode extends InsnNode {
private final Object data;
private ArgType elemType;
public FillArrayNode(int resReg, FillArrayDataPayloadDecodedInstruction payload) {
super(InsnType.FILL_ARRAY, 0);
ArgType elType;
switch (payload.getElementWidthUnit()) {
case 1:
elType = ArgType.unknown(PrimitiveType.BOOLEAN, PrimitiveType.BYTE);
break;
case 2:
elType = ArgType.unknown(PrimitiveType.SHORT, PrimitiveType.CHAR);
break;
case 4:
elType = ArgType.unknown(PrimitiveType.INT, PrimitiveType.FLOAT);
break;
case 8:
elType = ArgType.unknown(PrimitiveType.LONG, PrimitiveType.DOUBLE);
break;
default:
throw new JadxRuntimeException("Unknown array element width: " + payload.getElementWidthUnit());
}
setResult(InsnArg.reg(resReg, ArgType.array(elType)));
this.data = payload.getData();
this.elemType = elType;
}
public Object getData() {
return data;
}
public ArgType getElementType() {
return elemType;
}
public void mergeElementType(ArgType foundElemType) {
ArgType r = ArgType.merge(elemType, foundElemType);
if (r != null) {
elemType = r;
}
}
}
@@ -0,0 +1,27 @@
package jadx.core.dex.instructions;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.utils.InsnUtils;
public class GotoNode extends InsnNode {
protected int target;
public GotoNode(int target) {
this(InsnType.GOTO, target);
}
protected GotoNode(InsnType type, int target) {
super(type);
this.target = target;
}
public int getTarget() {
return target;
}
@Override
public String toString() {
return super.toString() + "-> " + InsnUtils.formatOffset(target);
}
}
@@ -0,0 +1,106 @@
package jadx.core.dex.instructions;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.LiteralArg;
import jadx.core.dex.instructions.args.PrimitiveType;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.utils.InsnUtils;
import com.android.dx.io.instructions.DecodedInstruction;
import static jadx.core.utils.BlockUtils.getBlockByOffset;
import static jadx.core.utils.BlockUtils.selectOther;
public class IfNode extends GotoNode {
protected boolean zeroCmp;
protected IfOp op;
private BlockNode thenBlock;
private BlockNode elseBlock;
public IfNode(int targ, InsnArg then, InsnArg els) {
super(InsnType.IF, targ);
addArg(then);
if (els == null) {
zeroCmp = true;
} else {
zeroCmp = false;
addArg(els);
}
}
public IfNode(DecodedInstruction insn, IfOp op) {
super(InsnType.IF, insn.getTarget());
this.op = op;
ArgType type = ArgType.unknown(
PrimitiveType.INT, PrimitiveType.OBJECT, PrimitiveType.ARRAY,
PrimitiveType.BOOLEAN, PrimitiveType.SHORT, PrimitiveType.CHAR);
addReg(insn, 0, type);
if (insn.getRegisterCount() == 1) {
zeroCmp = true;
} else {
zeroCmp = false;
addReg(insn, 1, type);
}
}
public IfOp getOp() {
return op;
}
public boolean isZeroCmp() {
return zeroCmp;
}
public void invertCondition() {
op = op.invert();
BlockNode tmp = thenBlock;
thenBlock = elseBlock;
elseBlock = tmp;
target = thenBlock.getStartOffset();
}
public void changeCondition(InsnArg arg1, InsnArg arg2, IfOp op) {
this.op = op;
this.zeroCmp = arg2.isLiteral() && ((LiteralArg) arg2).getLiteral() == 0;
setArg(0, arg1);
if (!zeroCmp) {
if (getArgsCount() == 2) {
setArg(1, arg2);
} else {
addArg(arg2);
}
}
}
public void initBlocks(BlockNode curBlock) {
thenBlock = getBlockByOffset(target, curBlock.getSuccessors());
if (curBlock.getSuccessors().size() == 1) {
elseBlock = thenBlock;
} else {
elseBlock = selectOther(thenBlock, curBlock.getSuccessors());
}
target = thenBlock.getStartOffset();
}
public BlockNode getThenBlock() {
return thenBlock;
}
public BlockNode getElseBlock() {
return elseBlock;
}
@Override
public String toString() {
return InsnUtils.formatOffset(offset) + ": "
+ InsnUtils.insnTypeToString(insnType)
+ getArg(0) + " " + op.getSymbol()
+ " " + (zeroCmp ? "0" : getArg(1))
+ " -> " + (thenBlock != null ? thenBlock : InsnUtils.formatOffset(target));
}
}
@@ -1,4 +1,6 @@
package jadx.dex.instructions;
package jadx.core.dex.instructions;
import jadx.core.utils.exceptions.JadxRuntimeException;
public enum IfOp {
EQ("=="),
@@ -36,7 +38,7 @@ public enum IfOp {
return IfOp.LT;
default:
return null;
throw new JadxRuntimeException("Unknown if operations type: " + this);
}
}
}
@@ -0,0 +1,23 @@
package jadx.core.dex.instructions;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.utils.InsnUtils;
public class IndexInsnNode extends InsnNode {
private final Object index;
public IndexInsnNode(InsnType type, Object index, int argCount) {
super(type, argCount);
this.index = index;
}
public Object getIndex() {
return index;
}
@Override
public String toString() {
return super.toString() + " " + InsnUtils.indexToString(index);
}
}
@@ -1,14 +1,16 @@
package jadx.dex.instructions;
package jadx.core.dex.instructions;
import jadx.dex.info.FieldInfo;
import jadx.dex.instructions.args.ArgType;
import jadx.dex.instructions.args.InsnArg;
import jadx.dex.instructions.args.PrimitiveType;
import jadx.dex.instructions.args.RegisterArg;
import jadx.dex.nodes.DexNode;
import jadx.dex.nodes.InsnNode;
import jadx.dex.nodes.MethodNode;
import jadx.utils.exceptions.DecodeException;
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.exceptions.DecodeException;
import com.android.dx.io.Code;
import com.android.dx.io.OpcodeInfo;
@@ -72,11 +74,11 @@ public class InsnDecoder {
case Opcodes.FILL_ARRAY_DATA_PAYLOAD:
return null;
// move-result will be process in invoke and filled-new-array instructions
// move-result will be process in invoke and filled-new-array instructions
case Opcodes.MOVE_RESULT:
case Opcodes.MOVE_RESULT_WIDE:
case Opcodes.MOVE_RESULT_OBJECT:
return new InsnNode(method, InsnType.NOP, 0);
return new InsnNode(InsnType.NOP, 0);
case Opcodes.CONST:
case Opcodes.CONST_4:
@@ -94,13 +96,13 @@ public class InsnDecoder {
case Opcodes.CONST_STRING:
case Opcodes.CONST_STRING_JUMBO: {
InsnNode node = new IndexInsnNode(method, InsnType.CONST, dex.getString(insn.getIndex()), 0);
InsnNode node = new ConstStringNode(dex.getString(insn.getIndex()));
node.setResult(InsnArg.reg(insn, 0, ArgType.STRING));
return node;
}
case Opcodes.CONST_CLASS: {
InsnNode node = new IndexInsnNode(method, InsnType.CONST, dex.getType(insn.getIndex()), 0);
InsnNode node = new ConstClassNode(dex.getType(insn.getIndex()));
node.setResult(InsnArg.reg(insn, 0, ArgType.CLASS));
return node;
}
@@ -144,20 +146,15 @@ public class InsnDecoder {
case Opcodes.ADD_INT_LIT8:
case Opcodes.ADD_INT_LIT16:
return arith_lit(insn, ArithOp.ADD, ArgType.INT);
return arithLit(insn, ArithOp.ADD, ArgType.INT);
case Opcodes.SUB_INT:
case Opcodes.SUB_INT_2ADDR:
return arith(insn, ArithOp.SUB, ArgType.INT);
case Opcodes.RSUB_INT:
return new ArithNode(method, ArithOp.SUB,
InsnArg.reg(insn, 0, ArgType.INT),
InsnArg.reg(insn, 2, ArgType.INT),
InsnArg.reg(insn, 1, ArgType.INT));
case Opcodes.RSUB_INT_LIT8:
return new ArithNode(method, ArithOp.SUB,
case Opcodes.RSUB_INT: // LIT16
return new ArithNode(ArithOp.SUB,
InsnArg.reg(insn, 0, ArgType.INT),
InsnArg.lit(insn, ArgType.INT),
InsnArg.reg(insn, 1, ArgType.INT));
@@ -192,7 +189,7 @@ public class InsnDecoder {
case Opcodes.MUL_INT_LIT8:
case Opcodes.MUL_INT_LIT16:
return arith_lit(insn, ArithOp.MUL, ArgType.INT);
return arithLit(insn, ArithOp.MUL, ArgType.INT);
case Opcodes.DIV_INT:
case Opcodes.DIV_INT_2ADDR:
@@ -228,11 +225,11 @@ public class InsnDecoder {
case Opcodes.DIV_INT_LIT8:
case Opcodes.DIV_INT_LIT16:
return arith_lit(insn, ArithOp.DIV, ArgType.INT);
return arithLit(insn, ArithOp.DIV, ArgType.INT);
case Opcodes.REM_INT_LIT8:
case Opcodes.REM_INT_LIT16:
return arith_lit(insn, ArithOp.REM, ArgType.INT);
return arithLit(insn, ArithOp.REM, ArgType.INT);
case Opcodes.AND_INT:
case Opcodes.AND_INT_2ADDR:
@@ -240,11 +237,11 @@ public class InsnDecoder {
case Opcodes.AND_INT_LIT8:
case Opcodes.AND_INT_LIT16:
return arith_lit(insn, ArithOp.AND, ArgType.INT);
return arithLit(insn, ArithOp.AND, ArgType.INT);
case Opcodes.XOR_INT_LIT8:
case Opcodes.XOR_INT_LIT16:
return arith_lit(insn, ArithOp.XOR, ArgType.INT);
return arithLit(insn, ArithOp.XOR, ArgType.INT);
case Opcodes.AND_LONG:
case Opcodes.AND_LONG_2ADDR:
@@ -256,7 +253,7 @@ public class InsnDecoder {
case Opcodes.OR_INT_LIT8:
case Opcodes.OR_INT_LIT16:
return arith_lit(insn, ArithOp.OR, ArgType.INT);
return arithLit(insn, ArithOp.OR, ArgType.INT);
case Opcodes.XOR_INT:
case Opcodes.XOR_INT_2ADDR:
@@ -295,11 +292,11 @@ public class InsnDecoder {
return arith(insn, ArithOp.SHR, ArgType.LONG);
case Opcodes.SHL_INT_LIT8:
return arith_lit(insn, ArithOp.SHL, ArgType.INT);
return arithLit(insn, ArithOp.SHL, ArgType.INT);
case Opcodes.SHR_INT_LIT8:
return arith_lit(insn, ArithOp.SHR, ArgType.INT);
return arithLit(insn, ArithOp.SHR, ArgType.INT);
case Opcodes.USHR_INT_LIT8:
return arith_lit(insn, ArithOp.USHR, ArgType.INT);
return arithLit(insn, ArithOp.USHR, ArgType.INT);
case Opcodes.NEG_INT:
return neg(insn, ArgType.INT);
@@ -346,27 +343,27 @@ public class InsnDecoder {
case Opcodes.IF_EQ:
case Opcodes.IF_EQZ:
return new IfNode(method, insn, IfOp.EQ);
return new IfNode(insn, IfOp.EQ);
case Opcodes.IF_NE:
case Opcodes.IF_NEZ:
return new IfNode(method, insn, IfOp.NE);
return new IfNode(insn, IfOp.NE);
case Opcodes.IF_GT:
case Opcodes.IF_GTZ:
return new IfNode(method, insn, IfOp.GT);
return new IfNode(insn, IfOp.GT);
case Opcodes.IF_GE:
case Opcodes.IF_GEZ:
return new IfNode(method, insn, IfOp.GE);
return new IfNode(insn, IfOp.GE);
case Opcodes.IF_LT:
case Opcodes.IF_LTZ:
return new IfNode(method, insn, IfOp.LT);
return new IfNode(insn, IfOp.LT);
case Opcodes.IF_LE:
case Opcodes.IF_LEZ:
return new IfNode(method, insn, IfOp.LE);
return new IfNode(insn, IfOp.LE);
case Opcodes.CMP_LONG:
return cmp(insn, InsnType.CMP_L, ArgType.LONG);
@@ -383,7 +380,7 @@ public class InsnDecoder {
case Opcodes.GOTO:
case Opcodes.GOTO_16:
case Opcodes.GOTO_32:
return new GotoNode(method, insn.getTarget());
return new GotoNode(insn.getTarget());
case Opcodes.THROW:
return insn(InsnType.THROW, null,
@@ -391,20 +388,21 @@ public class InsnDecoder {
case Opcodes.MOVE_EXCEPTION:
return insn(InsnType.MOVE_EXCEPTION,
InsnArg.reg(insn, 0, ArgType.unknown(PrimitiveType.OBJECT)));
InsnArg.reg(insn, 0, ArgType.unknown(PrimitiveType.OBJECT)),
new NamedArg("e", ArgType.unknown(PrimitiveType.OBJECT)));
case Opcodes.RETURN_VOID:
return new InsnNode(method, InsnType.RETURN, 0);
return new InsnNode(InsnType.RETURN, 0);
case Opcodes.RETURN:
case Opcodes.RETURN_WIDE:
case Opcodes.RETURN_OBJECT:
return insn(InsnType.RETURN,
null,
InsnArg.reg(insn, 0, method.getMethodInfo().getReturnType()));
InsnArg.reg(insn, 0, method.getReturnType()));
case Opcodes.INSTANCE_OF: {
InsnNode node = new IndexInsnNode(method, InsnType.INSTANCE_OF, dex.getType(insn.getIndex()), 1);
InsnNode node = new IndexInsnNode(InsnType.INSTANCE_OF, dex.getType(insn.getIndex()), 1);
node.setResult(InsnArg.reg(insn, 0, ArgType.BOOLEAN));
node.addArg(InsnArg.reg(insn, 1, ArgType.UNKNOWN_OBJECT));
return node;
@@ -412,7 +410,7 @@ public class InsnDecoder {
case Opcodes.CHECK_CAST: {
ArgType castType = dex.getType(insn.getIndex());
InsnNode node = new IndexInsnNode(method, InsnType.CHECK_CAST, castType, 1);
InsnNode node = new IndexInsnNode(InsnType.CHECK_CAST, castType, 1);
node.setResult(InsnArg.reg(insn, 0, castType));
node.addArg(InsnArg.reg(insn, 0, ArgType.UNKNOWN_OBJECT));
return node;
@@ -426,7 +424,7 @@ public class InsnDecoder {
case Opcodes.IGET_WIDE:
case Opcodes.IGET_OBJECT: {
FieldInfo field = FieldInfo.fromDex(dex, insn.getIndex());
InsnNode node = new IndexInsnNode(method, InsnType.IGET, field, 1);
InsnNode node = new IndexInsnNode(InsnType.IGET, field, 1);
node.setResult(InsnArg.reg(insn, 0, field.getType()));
node.addArg(InsnArg.reg(insn, 1, field.getDeclClass().getType()));
return node;
@@ -440,7 +438,7 @@ public class InsnDecoder {
case Opcodes.IPUT_WIDE:
case Opcodes.IPUT_OBJECT: {
FieldInfo field = FieldInfo.fromDex(dex, insn.getIndex());
InsnNode node = new IndexInsnNode(method, InsnType.IPUT, field, 2);
InsnNode node = new IndexInsnNode(InsnType.IPUT, field, 2);
node.addArg(InsnArg.reg(insn, 0, field.getType()));
node.addArg(InsnArg.reg(insn, 1, field.getDeclClass().getType()));
return node;
@@ -454,7 +452,7 @@ public class InsnDecoder {
case Opcodes.SGET_WIDE:
case Opcodes.SGET_OBJECT: {
FieldInfo field = FieldInfo.fromDex(dex, insn.getIndex());
InsnNode node = new IndexInsnNode(method, InsnType.SGET, field, 0);
InsnNode node = new IndexInsnNode(InsnType.SGET, field, 0);
node.setResult(InsnArg.reg(insn, 0, field.getType()));
return node;
}
@@ -467,13 +465,13 @@ public class InsnDecoder {
case Opcodes.SPUT_WIDE:
case Opcodes.SPUT_OBJECT: {
FieldInfo field = FieldInfo.fromDex(dex, insn.getIndex());
InsnNode node = new IndexInsnNode(method, InsnType.SPUT, field, 1);
InsnNode node = new IndexInsnNode(InsnType.SPUT, field, 1);
node.addArg(InsnArg.reg(insn, 0, field.getType()));
return node;
}
case Opcodes.ARRAY_LENGTH: {
InsnNode node = new InsnNode(method, InsnType.ARRAY_LENGTH, 1);
InsnNode node = new InsnNode(InsnType.ARRAY_LENGTH, 1);
node.setResult(InsnArg.reg(insn, 0, ArgType.INT));
node.addArg(InsnArg.reg(insn, 1, ArgType.unknown(PrimitiveType.ARRAY)));
return node;
@@ -573,32 +571,33 @@ public class InsnDecoder {
private InsnNode decodeSwitch(DecodedInstruction insn, int offset, boolean packed) {
int payloadOffset = insn.getTarget();
DecodedInstruction payload = insnArr[payloadOffset];
int[] keys;
Object[] keys;
int[] targets;
if (packed) {
PackedSwitchPayloadDecodedInstruction ps = (PackedSwitchPayloadDecodedInstruction) payload;
targets = ps.getTargets();
keys = new int[targets.length];
keys = new Object[targets.length];
int k = ps.getFirstKey();
for (int i = 0; i < keys.length; i++)
keys[i] = k++;
} else {
SparseSwitchPayloadDecodedInstruction ss = (SparseSwitchPayloadDecodedInstruction) payload;
targets = ss.getTargets();
keys = ss.getKeys();
keys = new Object[targets.length];
for (int i = 0; i < keys.length; i++)
keys[i] = ss.getKeys()[i];
}
// convert from relative to absolute offsets
for (int i = 0; i < targets.length; i++) {
targets[i] = targets[i] - payloadOffset + offset;
}
int nextOffset = getNextInsnOffset(insnArr, offset);
return new SwitchNode(method, InsnArg.reg(insn, 0, ArgType.NARROW),
keys, targets, nextOffset);
return new SwitchNode(InsnArg.reg(insn, 0, ArgType.NARROW), keys, targets, nextOffset);
}
private InsnNode fillArray(DecodedInstruction insn) {
DecodedInstruction payload = insnArr[insn.getTarget()];
return new FillArrayOp(method, insn.getA(), (FillArrayDataPayloadDecodedInstruction) payload);
return new FillArrayNode(insn.getA(), (FillArrayDataPayloadDecodedInstruction) payload);
}
private InsnNode filledNewArray(DecodedInstruction insn, int offset, boolean isRange) {
@@ -622,7 +621,7 @@ public class InsnDecoder {
}
private InsnNode cmp(DecodedInstruction insn, InsnType itype, ArgType argType) {
InsnNode inode = new InsnNode(method, itype, 2);
InsnNode inode = new InsnNode(itype, 2);
inode.setResult(InsnArg.reg(insn, 0, ArgType.INT));
inode.addArg(InsnArg.reg(insn, 1, argType));
inode.addArg(InsnArg.reg(insn, 2, argType));
@@ -630,7 +629,7 @@ public class InsnDecoder {
}
private InsnNode cast(DecodedInstruction insn, ArgType from, ArgType to) {
InsnNode inode = new IndexInsnNode(method, InsnType.CAST, to, 1);
InsnNode inode = new IndexInsnNode(InsnType.CAST, to, 1);
inode.setResult(InsnArg.reg(insn, 0, to));
inode.addArg(InsnArg.reg(insn, 1, from));
return inode;
@@ -638,11 +637,12 @@ public class InsnDecoder {
private InsnNode invoke(DecodedInstruction insn, int offset, InvokeType type, boolean isRange) {
int resReg = getMoveResultRegister(insnArr, offset);
return new InvokeNode(method, insn, type, isRange, resReg);
MethodInfo mth = MethodInfo.fromDex(dex, insn.getIndex());
return new InvokeNode(mth, insn, type, isRange, resReg);
}
private InsnNode arrayGet(DecodedInstruction insn, ArgType argType) {
InsnNode inode = new InsnNode(method, InsnType.AGET, 2);
InsnNode inode = new InsnNode(InsnType.AGET, 2);
inode.setResult(InsnArg.reg(insn, 0, argType));
inode.addArg(InsnArg.reg(insn, 1, ArgType.unknown(PrimitiveType.ARRAY)));
inode.addArg(InsnArg.reg(insn, 2, ArgType.INT));
@@ -650,7 +650,7 @@ public class InsnDecoder {
}
private InsnNode arrayPut(DecodedInstruction insn, ArgType argType) {
InsnNode inode = new InsnNode(method, InsnType.APUT, 3);
InsnNode inode = new InsnNode(InsnType.APUT, 3);
inode.addArg(InsnArg.reg(insn, 1, ArgType.unknown(PrimitiveType.ARRAY)));
inode.addArg(InsnArg.reg(insn, 2, ArgType.INT));
inode.addArg(InsnArg.reg(insn, 0, argType));
@@ -658,27 +658,41 @@ public class InsnDecoder {
}
private InsnNode arith(DecodedInstruction insn, ArithOp op, ArgType type) {
return new ArithNode(method, insn, op, type, false);
return new ArithNode(insn, op, type, false);
}
private InsnNode arith_lit(DecodedInstruction insn, ArithOp op, ArgType type) {
return new ArithNode(method, insn, op, type, true);
private InsnNode arithLit(DecodedInstruction insn, ArithOp op, ArgType type) {
return new ArithNode(insn, op, type, true);
}
private InsnNode neg(DecodedInstruction insn, ArgType type) {
InsnNode inode = new InsnNode(method, InsnType.NEG, 1);
InsnNode inode = new InsnNode(InsnType.NEG, 1);
inode.setResult(InsnArg.reg(insn, 0, type));
inode.addArg(InsnArg.reg(insn, 1, type));
return inode;
}
private InsnNode insn(InsnType type, RegisterArg res) {
InsnNode node = new InsnNode(type, 0);
node.setResult(res);
return node;
}
private InsnNode insn(InsnType type, RegisterArg res, InsnArg arg) {
InsnNode node = new InsnNode(type, 1);
node.setResult(res);
node.addArg(arg);
return node;
}
private InsnNode insn(InsnType type, RegisterArg res, InsnArg... args) {
InsnNode inode = new InsnNode(method, type, args == null ? 0 : args.length);
inode.setResult(res);
if (args != null)
InsnNode node = new InsnNode(type, args == null ? 0 : args.length);
node.setResult(res);
if (args != null) {
for (InsnArg arg : args)
inode.addArg(arg);
return inode;
node.addArg(arg);
}
return node;
}
private int getMoveResultRegister(DecodedInstruction[] insnArr, int offset) {
@@ -1,9 +1,11 @@
package jadx.dex.instructions;
package jadx.core.dex.instructions;
public enum InsnType {
NOP, // replacement for removed instructions
CONST,
CONST_STR,
CONST_CLASS,
ARITH,
NEG,
@@ -50,6 +52,11 @@ public enum InsnType {
CONSTRUCTOR,
BREAK,
CONTINUE,
STR_CONCAT, // strings concatenation
TERNARY,
ARGS, // just generate arguments
NEW_MULTIDIM_ARRAY // TODO: now multidimensional arrays created using Array.newInstance function
}
@@ -1,12 +1,11 @@
package jadx.dex.instructions;
package jadx.core.dex.instructions;
import jadx.dex.info.MethodInfo;
import jadx.dex.instructions.args.ArgType;
import jadx.dex.instructions.args.InsnArg;
import jadx.dex.nodes.InsnNode;
import jadx.dex.nodes.MethodNode;
import jadx.utils.InsnUtils;
import jadx.utils.Utils;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.utils.InsnUtils;
import jadx.core.utils.Utils;
import com.android.dx.io.instructions.DecodedInstruction;
@@ -15,10 +14,9 @@ public class InvokeNode extends InsnNode {
private final InvokeType type;
private final MethodInfo mth;
public InvokeNode(MethodNode method, DecodedInstruction insn, InvokeType type, boolean isRange,
int resReg) {
super(method, InsnType.INVOKE);
this.mth = MethodInfo.fromDex(method.dex(), insn.getIndex());
public InvokeNode(MethodInfo mth, DecodedInstruction insn, InvokeType type, boolean isRange, int resReg) {
super(InsnType.INVOKE, mth.getArgsCount() + (type != InvokeType.STATIC ? 1 : 0));
this.mth = mth;
this.type = type;
if (resReg >= 0)
@@ -1,4 +1,4 @@
package jadx.dex.instructions;
package jadx.core.dex.instructions;
public enum InvokeType {
STATIC,
@@ -1,20 +1,19 @@
package jadx.dex.instructions;
package jadx.core.dex.instructions;
import jadx.dex.instructions.args.InsnArg;
import jadx.dex.nodes.InsnNode;
import jadx.dex.nodes.MethodNode;
import jadx.utils.InsnUtils;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.utils.InsnUtils;
import java.util.Arrays;
public class SwitchNode extends InsnNode {
private final int[] keys;
private final Object[] keys;
private final int[] targets;
private final int def; // next instruction
public SwitchNode(MethodNode mth, InsnArg arg, int[] keys, int[] targets, int def) {
super(mth, InsnType.SWITCH, 1);
public SwitchNode(InsnArg arg, Object[] keys, int[] targets, int def) {
super(InsnType.SWITCH, 1);
this.keys = keys;
this.targets = targets;
this.def = def;
@@ -25,7 +24,7 @@ public class SwitchNode extends InsnNode {
return keys.length;
}
public int[] getKeys() {
public Object[] getKeys() {
return keys;
}
@@ -0,0 +1,670 @@
package jadx.core.dex.instructions.args;
import jadx.core.Consts;
import jadx.core.clsp.ClspGraph;
import jadx.core.utils.Utils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public abstract class ArgType {
private static final Logger LOG = LoggerFactory.getLogger(ArgType.class);
public static final ArgType INT = primitive(PrimitiveType.INT);
public static final ArgType BOOLEAN = primitive(PrimitiveType.BOOLEAN);
public static final ArgType BYTE = primitive(PrimitiveType.BYTE);
public static final ArgType SHORT = primitive(PrimitiveType.SHORT);
public static final ArgType CHAR = primitive(PrimitiveType.CHAR);
public static final ArgType FLOAT = primitive(PrimitiveType.FLOAT);
public static final ArgType DOUBLE = primitive(PrimitiveType.DOUBLE);
public static final ArgType LONG = primitive(PrimitiveType.LONG);
public static final ArgType VOID = primitive(PrimitiveType.VOID);
public static final ArgType OBJECT = object(Consts.CLASS_OBJECT);
public static final ArgType CLASS = object(Consts.CLASS_CLASS);
public static final ArgType STRING = object(Consts.CLASS_STRING);
public static final ArgType THROWABLE = object(Consts.CLASS_THROWABLE);
public static final ArgType UNKNOWN = unknown(PrimitiveType.values());
public static final ArgType UNKNOWN_OBJECT = unknown(PrimitiveType.OBJECT, PrimitiveType.ARRAY);
public static final ArgType NARROW = unknown(
PrimitiveType.INT, PrimitiveType.FLOAT,
PrimitiveType.BOOLEAN, PrimitiveType.SHORT, PrimitiveType.BYTE, PrimitiveType.CHAR,
PrimitiveType.OBJECT, PrimitiveType.ARRAY);
public static final ArgType NARROW_NUMBERS = unknown(
PrimitiveType.INT, PrimitiveType.FLOAT,
PrimitiveType.BOOLEAN, PrimitiveType.SHORT, PrimitiveType.BYTE, PrimitiveType.CHAR);
public static final ArgType WIDE = unknown(PrimitiveType.LONG, PrimitiveType.DOUBLE);
protected int hash;
private static ClspGraph clsp;
public static void setClsp(ClspGraph clsp) {
ArgType.clsp = clsp;
}
private static ArgType primitive(PrimitiveType stype) {
return new PrimitiveArg(stype);
}
public static ArgType object(String obj) {
return new ObjectArg(obj);
}
public static ArgType genericType(String type) {
return new GenericTypeArg(type);
}
public static ArgType generic(String sign) {
return parseSignature(sign);
}
public static ArgType generic(String obj, ArgType[] generics) {
return new GenericObjectArg(obj, generics);
}
public static ArgType array(ArgType vtype) {
return new ArrayArg(vtype);
}
public static ArgType unknown(PrimitiveType... types) {
return new UnknownArg(types);
}
private abstract static class KnownTypeArg extends ArgType {
@Override
public boolean isTypeKnown() {
return true;
}
}
private static final class PrimitiveArg extends KnownTypeArg {
private final PrimitiveType type;
public PrimitiveArg(PrimitiveType type) {
this.type = type;
this.hash = type.hashCode();
}
@Override
public PrimitiveType getPrimitiveType() {
return type;
}
@Override
public boolean isPrimitive() {
return true;
}
@Override
boolean internalEquals(Object obj) {
return type == ((PrimitiveArg) obj).type;
}
@Override
public String toString() {
return type.toString();
}
}
private static class ObjectArg extends KnownTypeArg {
private final String object;
public ObjectArg(String obj) {
this.object = Utils.cleanObjectName(obj);
this.hash = object.hashCode();
}
@Override
public String getObject() {
return object;
}
@Override
public boolean isObject() {
return true;
}
@Override
public PrimitiveType getPrimitiveType() {
return PrimitiveType.OBJECT;
}
@Override
boolean internalEquals(Object obj) {
return object.equals(((ObjectArg) obj).object);
}
@Override
public String toString() {
return object;
}
}
private static final class GenericTypeArg extends ObjectArg {
public GenericTypeArg(String obj) {
super(obj);
}
@Override
public boolean isGenericType() {
return true;
}
}
private static final class GenericObjectArg extends ObjectArg {
private final ArgType[] generics;
public GenericObjectArg(String obj, ArgType[] generics) {
super(obj);
this.generics = generics;
this.hash = obj.hashCode() + 31 * Arrays.hashCode(generics);
}
@Override
public ArgType[] getGenericTypes() {
return generics;
}
@Override
boolean internalEquals(Object obj) {
return super.internalEquals(obj)
&& Arrays.equals(generics, ((GenericObjectArg) obj).generics);
}
@Override
public String toString() {
return super.toString() + "<" + Utils.arrayToString(generics) + ">";
}
}
private static final class ArrayArg extends KnownTypeArg {
private final ArgType arrayElement;
public ArrayArg(ArgType arrayElement) {
this.arrayElement = arrayElement;
this.hash = arrayElement.hashCode();
}
@Override
public ArgType getArrayElement() {
return arrayElement;
}
@Override
public boolean isArray() {
return true;
}
@Override
public PrimitiveType getPrimitiveType() {
return PrimitiveType.ARRAY;
}
@Override
public int getArrayDimension() {
return 1 + arrayElement.getArrayDimension();
}
@Override
public ArgType getArrayRootElement() {
return arrayElement.getArrayRootElement();
}
@Override
boolean internalEquals(Object obj) {
return arrayElement.equals(((ArrayArg) obj).arrayElement);
}
@Override
public String toString() {
return arrayElement.toString() + "[]";
}
}
private static final class UnknownArg extends ArgType {
private final PrimitiveType[] possibleTypes;
public UnknownArg(PrimitiveType[] types) {
this.possibleTypes = types;
this.hash = Arrays.hashCode(possibleTypes);
}
@Override
public PrimitiveType[] getPossibleTypes() {
return possibleTypes;
}
@Override
public boolean isTypeKnown() {
return false;
}
@Override
public boolean contains(PrimitiveType type) {
for (PrimitiveType t : possibleTypes) {
if (t == type) {
return true;
}
}
return false;
}
@Override
public ArgType selectFirst() {
PrimitiveType f = possibleTypes[0];
if (f == PrimitiveType.OBJECT || f == PrimitiveType.ARRAY) {
return object(Consts.CLASS_OBJECT);
} else {
return primitive(f);
}
}
@Override
boolean internalEquals(Object obj) {
return Arrays.equals(possibleTypes, ((UnknownArg) obj).possibleTypes);
}
@Override
public String toString() {
if (possibleTypes.length == PrimitiveType.values().length) {
return "?";
} else {
return "?" + Arrays.toString(possibleTypes);
}
}
}
public boolean isTypeKnown() {
return false;
}
public PrimitiveType getPrimitiveType() {
return null;
}
public boolean isPrimitive() {
return false;
}
public String getObject() {
throw new UnsupportedOperationException();
}
public boolean isObject() {
return false;
}
public boolean isGenericType() {
return false;
}
public ArgType[] getGenericTypes() {
return null;
}
public boolean isArray() {
return false;
}
public int getArrayDimension() {
return 0;
}
public ArgType getArrayElement() {
return null;
}
public ArgType getArrayRootElement() {
return this;
}
public boolean contains(PrimitiveType type) {
throw new UnsupportedOperationException();
}
public ArgType selectFirst() {
throw new UnsupportedOperationException();
}
public PrimitiveType[] getPossibleTypes() {
return null;
}
public static ArgType merge(ArgType a, ArgType b) {
if (b == null || a == null) {
return null;
}
if (a.equals(b)) {
return a;
}
ArgType res = mergeInternal(a, b);
if (res == null) {
res = mergeInternal(b, a); // swap
}
return res;
}
private static ArgType mergeInternal(ArgType a, ArgType b) {
if (a == UNKNOWN) {
return b;
}
if (!a.isTypeKnown()) {
if (b.isTypeKnown()) {
if (a.contains(b.getPrimitiveType())) {
return b;
} else {
return null;
}
} else {
// both types unknown
List<PrimitiveType> types = new ArrayList<PrimitiveType>();
for (PrimitiveType type : a.getPossibleTypes()) {
if (b.contains(type)) {
types.add(type);
}
}
if (types.size() == 0) {
return null;
} else if (types.size() == 1) {
PrimitiveType nt = types.get(0);
if (nt == PrimitiveType.OBJECT || nt == PrimitiveType.ARRAY) {
return unknown(nt);
} else {
return primitive(nt);
}
} else {
return unknown(types.toArray(new PrimitiveType[types.size()]));
}
}
} else {
if (a.isGenericType()) {
return a;
}
if (b.isGenericType()) {
return b;
}
if (a.isObject() && b.isObject()) {
String aObj = a.getObject();
String bObj = b.getObject();
if (aObj.equals(bObj)) {
return (a.getGenericTypes() != null ? a : b);
} else if (aObj.equals(Consts.CLASS_OBJECT)) {
return b;
} else 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;
}
}
if (a.isPrimitive() && b.isPrimitive() && a.getRegCount() == b.getRegCount()) {
return primitive(PrimitiveType.getSmaller(a.getPrimitiveType(), b.getPrimitiveType()));
}
}
return null;
}
public static boolean isCastNeeded(ArgType from, ArgType to) {
if (from.equals(to)) {
return false;
}
if (from.isObject() && to.isObject()
&& clsp.isImplements(from.getObject(), to.getObject())) {
return false;
}
return true;
}
public static ArgType parse(String type) {
char f = type.charAt(0);
switch (f) {
case 'L':
return object(type);
case 'T':
return genericType(type.substring(1, type.length() - 1));
case '[':
return array(parse(type.substring(1)));
default:
return parse(f);
}
}
public static ArgType parseSignature(String sign) {
int b = sign.indexOf('<');
if (b == -1) {
return parse(sign);
}
if (sign.charAt(0) == '[') {
return array(parseSignature(sign.substring(1)));
}
String obj = sign.substring(0, b) + ";";
String genericsStr = sign.substring(b + 1, sign.length() - 2);
List<ArgType> generics = parseSignatureList(genericsStr);
if (generics != null) {
return generic(obj, generics.toArray(new ArgType[generics.size()]));
} else {
return object(obj);
}
}
public static List<ArgType> parseSignatureList(String str) {
try {
return parseSignatureListInner(str, true);
} catch (Throwable e) {
LOG.warn("Signature parse exception: {}", str, e);
return null;
}
}
private static List<ArgType> parseSignatureListInner(String str, boolean parsePrimitives) {
if (str.isEmpty()) {
return Collections.emptyList();
}
if (str.equals("*")) {
return Arrays.asList(UNKNOWN);
}
List<ArgType> signs = new ArrayList<ArgType>(3);
int obj = 0;
int objStart = 0;
int gen = 0;
int arr = 0;
int pos = 0;
ArgType type = null;
while (pos < str.length()) {
char c = str.charAt(pos);
switch (c) {
case 'L':
case 'T':
if (obj == 0 && gen == 0) {
obj++;
objStart = pos;
}
break;
case ';':
if (obj == 1 && gen == 0) {
obj--;
String o = str.substring(objStart, pos + 1);
type = parseSignature(o);
}
break;
case ':': // generic types map separator
if (gen == 0) {
obj = 0;
String o = str.substring(objStart, pos);
if (o.length() > 0) {
type = genericType(o);
}
}
break;
case '<':
gen++;
break;
case '>':
gen--;
break;
case '[':
if (obj == 0 && gen == 0) {
arr++;
}
break;
default:
if (parsePrimitives && obj == 0 && gen == 0) {
type = parse(c);
}
break;
}
if (type != null) {
if (arr == 0) {
signs.add(type);
} else {
for (int i = 0; i < arr; i++) {
type = array(type);
}
signs.add(type);
arr = 0;
}
type = null;
objStart = pos + 1;
}
pos++;
}
return signs;
}
public static Map<ArgType, List<ArgType>> parseGenericMap(String gen) {
try {
Map<ArgType, List<ArgType>> genericMap = null;
List<ArgType> genTypes = parseSignatureListInner(gen, false);
if (genTypes != null) {
genericMap = new LinkedHashMap<ArgType, List<ArgType>>(2);
ArgType prev = null;
List<ArgType> genList = new ArrayList<ArgType>(2);
for (ArgType arg : genTypes) {
if (arg.isGenericType()) {
if (prev != null) {
genericMap.put(prev, genList);
genList = new ArrayList<ArgType>();
}
prev = arg;
} else {
if (!arg.getObject().equals(Consts.CLASS_OBJECT)) {
genList.add(arg);
}
}
}
if (prev != null) {
genericMap.put(prev, genList);
}
}
return genericMap;
} catch (Throwable e) {
LOG.warn("Generic map parse exception: {}", gen, e);
return null;
}
}
private static ArgType parse(char f) {
switch (f) {
case 'Z':
return BOOLEAN;
case 'B':
return BYTE;
case 'C':
return CHAR;
case 'S':
return SHORT;
case 'I':
return INT;
case 'J':
return LONG;
case 'F':
return FLOAT;
case 'D':
return DOUBLE;
case 'V':
return VOID;
}
return null;
}
public int getRegCount() {
if (isPrimitive()) {
PrimitiveType type = getPrimitiveType();
if (type == PrimitiveType.LONG || type == PrimitiveType.DOUBLE) {
return 2;
} else {
return 1;
}
}
if (!isTypeKnown()) {
return 0;
}
return 1;
}
@Override
public String toString() {
return "ARG_TYPE";
}
@Override
public int hashCode() {
return hash;
}
abstract boolean internalEquals(Object obj);
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (hash != obj.hashCode()) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
return internalEquals(obj);
}
}
@@ -0,0 +1,36 @@
package jadx.core.dex.instructions.args;
import jadx.core.dex.info.FieldInfo;
public final class FieldArg extends RegisterArg {
private final FieldInfo field;
public FieldArg(FieldInfo field, int regNum) {
super(regNum, field.getType());
this.field = field;
}
public FieldInfo getField() {
return field;
}
public boolean isStatic() {
return regNum == -1;
}
@Override
public boolean isField() {
return true;
}
@Override
public boolean isRegister() {
return false;
}
@Override
public String toString() {
return "(" + field + ")";
}
}
@@ -0,0 +1,27 @@
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,7 +1,7 @@
package jadx.dex.instructions.args;
package jadx.core.dex.instructions.args;
import jadx.dex.nodes.InsnNode;
import jadx.utils.InsnUtils;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.utils.InsnUtils;
import com.android.dx.io.instructions.DecodedInstruction;
@@ -14,7 +14,6 @@ public abstract class InsnArg extends Typed {
protected InsnNode parentInsn;
public static RegisterArg reg(int regNum, ArgType type) {
assert regNum >= 0 : "Register number must be positive";
return new RegisterArg(regNum, type);
}
@@ -22,6 +21,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 LiteralArg lit(long literal, ArgType type) {
return new LiteralArg(literal, type);
}
@@ -30,7 +35,7 @@ public abstract class InsnArg extends Typed {
return lit(insn.getLiteral(), type);
}
public static InsnWrapArg wrap(InsnNode insn) {
private static InsnWrapArg wrap(InsnNode insn) {
return new InsnWrapArg(insn);
}
@@ -46,6 +51,14 @@ public abstract class InsnArg extends Typed {
return false;
}
public boolean isNamed() {
return false;
}
public boolean isField() {
return false;
}
public InsnNode getParentInsn() {
return parentInsn;
}
@@ -54,22 +67,44 @@ public abstract class InsnArg extends Typed {
this.parentInsn = parentInsn;
}
public InsnWrapArg wrapInstruction(InsnNode insn) {
assert parentInsn != insn : "Can't wrap instruction info itself";
int count = parentInsn.getArgsCount();
public InsnArg wrapInstruction(InsnNode insn) {
InsnNode parent = parentInsn;
assert parent != insn : "Can't wrap instruction info itself";
int count = parent.getArgsCount();
for (int i = 0; i < count; i++) {
if (parentInsn.getArg(i) == this) {
InsnWrapArg arg = wrap(insn);
parentInsn.setArg(i, arg);
if (parent.getArg(i) == this) {
InsnArg arg = wrapArg(insn);
parent.setArg(i, arg);
return arg;
}
}
return null;
}
public static InsnArg wrapArg(InsnNode insn) {
InsnArg arg;
switch (insn.getType()) {
case MOVE:
case CONST:
arg = insn.getArg(0);
break;
case CONST_STR:
arg = wrap(insn);
arg.getTypedVar().forceSetType(ArgType.STRING);
break;
case CONST_CLASS:
arg = wrap(insn);
arg.getTypedVar().forceSetType(ArgType.CLASS);
break;
default:
arg = wrap(insn);
break;
}
return arg;
}
public boolean isThis() {
// must be implemented in RegisterArg
return false;
}
}
@@ -1,8 +1,8 @@
package jadx.dex.instructions.args;
package jadx.core.dex.instructions.args;
import jadx.dex.nodes.InsnNode;
import jadx.core.dex.nodes.InsnNode;
public class InsnWrapArg extends InsnArg {
public final class InsnWrapArg extends InsnArg {
private final InsnNode wrappedInsn;
@@ -0,0 +1,54 @@
package jadx.core.dex.instructions.args;
import jadx.core.codegen.TypeGen;
import jadx.core.utils.exceptions.JadxRuntimeException;
public final class LiteralArg extends InsnArg {
private final long literal;
public LiteralArg(long value, ArgType type) {
if (value != 0) {
if (type.isObject()) {
throw new JadxRuntimeException("Wrong literal type: " + type + " for value: " + value);
} else if (!type.isTypeKnown()
&& !type.contains(PrimitiveType.LONG)
&& !type.contains(PrimitiveType.DOUBLE)) {
ArgType m = ArgType.merge(type, ArgType.NARROW_NUMBERS);
if (m != null) {
type = m;
}
}
}
this.literal = value;
this.typedVar = new TypedVar(type);
}
public long getLiteral() {
return literal;
}
@Override
public boolean isLiteral() {
return true;
}
public boolean isInteger() {
PrimitiveType type = typedVar.getType().getPrimitiveType();
return (type == PrimitiveType.INT
|| type == PrimitiveType.BYTE
|| type == PrimitiveType.CHAR
|| type == PrimitiveType.SHORT
|| type == PrimitiveType.LONG);
}
@Override
public String toString() {
try {
return "(" + TypeGen.literalToString(literal, getType()) + " " + typedVar + ")";
} catch (JadxRuntimeException ex) {
// can't convert literal to string
return "(" + literal + " " + typedVar + ")";
}
}
}
@@ -0,0 +1,29 @@
package jadx.core.dex.instructions.args;
public final class NamedArg extends InsnArg {
private String name;
public NamedArg(String name, ArgType type) {
this.name = name;
this.typedVar = new TypedVar(type);
}
public String getName() {
return name;
}
@Override
public boolean isNamed() {
return true;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "(" + name + " " + typedVar + ")";
}
}
@@ -1,4 +1,4 @@
package jadx.dex.instructions.args;
package jadx.core.dex.instructions.args;
public enum PrimitiveType {
BOOLEAN("Z", "boolean"),
@@ -45,6 +45,6 @@ public enum PrimitiveType {
@Override
public String toString() {
return this.name().toLowerCase();
return longName;
}
}
@@ -1,11 +1,22 @@
package jadx.dex.instructions.args;
package jadx.core.dex.instructions.args;
import jadx.dex.instructions.IndexInsnNode;
import jadx.dex.instructions.InsnType;
import jadx.dex.nodes.InsnNode;
import jadx.dex.visitors.InstructionRemover;
import jadx.core.dex.attributes.AttributeType;
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.nodes.DexNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.parser.FieldValueAttr;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class RegisterArg extends InsnArg {
private static final Logger LOG = LoggerFactory.getLogger(RegisterArg.class);
protected final int regNum;
public RegisterArg(int rn) {
@@ -41,17 +52,32 @@ public class RegisterArg extends InsnArg {
/**
* Return constant value from register assign or null if not constant
*
*
* @return LiteralArg, String or ArgType
*/
public Object getConstValue() {
public Object getConstValue(DexNode dex) {
InsnNode parInsn = getAssignInsn();
if (parInsn != null && parInsn.getType() == InsnType.CONST) {
if (parInsn.getArgsCount() == 0) {
// const in 'index' - string or class
return ((IndexInsnNode) parInsn).getIndex();
} else {
return parInsn.getArg(0);
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);
}
break;
}
}
return null;
@@ -61,17 +87,14 @@ public class RegisterArg extends InsnArg {
public boolean isThis() {
if (isRegister()) {
String name = getTypedVar().getName();
if (name != null && name.equals("this"))
if (name != null && name.equals("this")) {
return true;
}
// maybe it was moved from 'this' register
InsnNode ai = getAssignInsn();
if (ai != null && ai.getType() == InsnType.MOVE) {
if (ai.getArg(0).isThis()) {
// actually we need to remove this instruction but we can't
// because of iterating on instructions list
// so unbind insn and rely on code shrinker
InstructionRemover.unbindInsn(ai);
InsnArg arg = ai.getArg(0);
if (arg != this && arg.isThis()) {
return true;
}
}
@@ -0,0 +1,71 @@
package jadx.core.dex.instructions.args;
import java.util.List;
public abstract class Typed {
TypedVar typedVar;
public TypedVar getTypedVar() {
return typedVar;
}
public ArgType getType() {
return typedVar.getType();
}
public boolean merge(Typed var) {
return typedVar.merge(var.getTypedVar());
}
public boolean merge(ArgType var) {
return typedVar.merge(var);
}
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;
}
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;
}
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);
}
}
@@ -1,4 +1,4 @@
package jadx.dex.instructions.args;
package jadx.core.dex.instructions.args;
import java.util.ArrayList;
import java.util.List;
@@ -20,13 +20,8 @@ public class TypedVar {
/**
* This method must be used very carefully
*/
public boolean forceSetType(ArgType newType) {
if (newType != null && !type.equals(newType)) {
type = newType;
return true;
} else {
return false;
}
public void forceSetType(ArgType newType) {
type = newType;
}
public boolean merge(TypedVar typedVar) {
@@ -55,28 +50,45 @@ public class TypedVar {
this.name = name;
}
public void mergeName(TypedVar arg) {
String name = arg.getName();
if (name != null) {
setName(name);
} 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());
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 (getClass() != obj.getClass()) 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;
if (type == null) {
if (other.type != null) return false;
} else if (!type.equals(other.type)) return false;
} else if (!name.equals(other.name)) {
return false;
}
return true;
}
@Override
public String toString() {
return (name != null ? "'" + name + "' " : "") + type.toString();
StringBuilder sb = new StringBuilder();
if (name != null)
sb.append('\'').append(name).append("' ");
sb.append(type);
return sb.toString();
}
}
@@ -1,16 +1,18 @@
package jadx.dex.instructions.mods;
package jadx.core.dex.instructions.mods;
import jadx.dex.info.ClassInfo;
import jadx.dex.info.MethodInfo;
import jadx.dex.instructions.InsnType;
import jadx.dex.instructions.InvokeNode;
import jadx.dex.instructions.args.RegisterArg;
import jadx.dex.nodes.InsnNode;
import jadx.dex.nodes.MethodNode;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.InvokeNode;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
public class ConstructorInsn extends InsnNode {
private final MethodInfo callMth;
private final CallType callType;
private final RegisterArg instanceArg;
private static enum CallType {
CONSTRUCTOR, // just new instance
@@ -19,17 +21,17 @@ public class ConstructorInsn extends InsnNode {
SELF // call itself
}
private final CallType callType;
public ConstructorInsn(MethodNode mth, InvokeNode invoke) {
super(mth, InsnType.CONSTRUCTOR, invoke.getArgsCount() - 1);
super(InsnType.CONSTRUCTOR, invoke.getArgsCount() - 1);
this.callMth = invoke.getCallMth();
ClassInfo classType = callMth.getDeclClass();
instanceArg = (RegisterArg) invoke.getArg(0);
instanceArg.setParentInsn(this);
if (invoke.getArg(0).isThis()) {
if (instanceArg.isThis()) {
if (classType.equals(mth.getParentClass().getClassInfo())) {
// self constructor
if (callMth.getShortId().equals(mth.getMethodInfo().getShortId())) {
// self constructor
callType = CallType.SELF;
} else {
callType = CallType.THIS;
@@ -39,9 +41,8 @@ public class ConstructorInsn extends InsnNode {
}
} else {
callType = CallType.CONSTRUCTOR;
setResult((RegisterArg) invoke.getArg(0));
setResult(instanceArg);
}
for (int i = 1; i < invoke.getArgsCount(); i++) {
addArg(invoke.getArg(i));
}
@@ -52,6 +53,10 @@ public class ConstructorInsn extends InsnNode {
return callMth;
}
public RegisterArg getInstanceArg() {
return instanceArg;
}
public ClassInfo getClassType() {
return callMth.getDeclClass();
}
@@ -1,10 +1,10 @@
package jadx.dex.nodes;
package jadx.core.dex.nodes;
import jadx.dex.attributes.AttrNode;
import jadx.dex.attributes.AttributeType;
import jadx.dex.attributes.BlockRegState;
import jadx.dex.attributes.LoopAttr;
import jadx.utils.InsnUtils;
import jadx.core.dex.attributes.AttrNode;
import jadx.core.dex.attributes.AttributeType;
import jadx.core.dex.attributes.BlockRegState;
import jadx.core.dex.attributes.LoopAttr;
import jadx.core.utils.InsnUtils;
import java.util.ArrayList;
import java.util.BitSet;
@@ -19,6 +19,7 @@ public class BlockNode extends AttrNode implements IBlock {
private List<BlockNode> predecessors = new ArrayList<BlockNode>(1);
private List<BlockNode> successors = new ArrayList<BlockNode>(1);
private List<BlockNode> cleanSuccessors;
private BitSet doms; // all dominators
private BlockNode idom; // immediate dominator
@@ -27,7 +28,7 @@ public class BlockNode extends AttrNode implements IBlock {
private BlockRegState startState;
private BlockRegState endState;
public BlockNode(MethodNode mth, int id, int offset) {
public BlockNode(int id, int offset) {
this.id = id;
this.startOffset = offset;
}
@@ -48,8 +49,6 @@ public class BlockNode extends AttrNode implements IBlock {
return successors;
}
private List<BlockNode> cleanSuccessors;
public List<BlockNode> getCleanSuccessors() {
return cleanSuccessors;
}
@@ -0,0 +1,398 @@
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.annotations.Annotation;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.info.AccessInfo.AFType;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.LiteralArg;
import jadx.core.dex.instructions.args.PrimitiveType;
import jadx.core.dex.nodes.parser.AnnotationsParser;
import jadx.core.dex.nodes.parser.FieldValueAttr;
import jadx.core.dex.nodes.parser.StaticValuesParser;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.DecodeException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
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;
public class ClassNode extends LineAttrNode implements ILoadable {
private static final Logger LOG = LoggerFactory.getLogger(ClassNode.class);
private final DexNode dex;
private final ClassInfo clsInfo;
private ClassInfo superClass;
private List<ClassInfo> interfaces;
private Map<ArgType, List<ArgType>> genericMap;
private final List<MethodNode> methods = new ArrayList<MethodNode>();
private final List<FieldNode> fields = new ArrayList<FieldNode>();
private final AccessInfo accessFlags;
private List<ClassNode> innerClasses = Collections.emptyList();
private final Map<Object, FieldNode> constFields = new HashMap<Object, FieldNode>();
private CodeWriter code; // generated code
public ClassNode(DexNode dex, ClassDef cls) throws DecodeException {
this.dex = dex;
this.clsInfo = ClassInfo.fromDex(dex, cls.getTypeIndex());
try {
this.superClass = cls.getSupertypeIndex() == DexNode.NO_INDEX
? null
: ClassInfo.fromDex(dex, cls.getSupertypeIndex());
this.interfaces = new ArrayList<ClassInfo>(cls.getInterfaces().length);
for (short interfaceIdx : cls.getInterfaces()) {
this.interfaces.add(ClassInfo.fromDex(dex, interfaceIdx));
}
if (cls.getClassDataOffset() != 0) {
ClassData clsData = dex.readClassData(cls);
for (Method mth : clsData.getDirectMethods())
methods.add(new MethodNode(this, mth));
for (Method mth : clsData.getVirtualMethods())
methods.add(new MethodNode(this, mth));
for (Field f : clsData.getStaticFields())
fields.add(new FieldNode(this, f));
loadStaticValues(cls, fields);
for (Field f : clsData.getInstanceFields())
fields.add(new FieldNode(this, f));
}
loadAnnotations(cls);
parseClassSignature();
setFieldsTypesFromSignature();
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));
LOG.debug("Class '{}' compiled from '{}'", this, fileName);
}
}
int accFlagsValue;
Annotation a = getAttributes().getAnnotation(Consts.DALVIK_INNER_CLASS);
if (a != null)
accFlagsValue = (Integer) a.getValues().get("accessFlags");
else
accFlagsValue = cls.getAccessFlags();
this.accessFlags = new AccessInfo(accFlagsValue, AFType.CLASS);
} catch (Exception e) {
throw new DecodeException("Error decode class: " + getFullName(), e);
}
}
private void loadAnnotations(ClassDef cls) {
int offset = cls.getAnnotationsOffset();
if (offset != 0) {
try {
new AnnotationsParser(this, offset);
} catch (DecodeException e) {
LOG.error("Error parsing annotations in " + this, e);
}
}
}
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);
}
}
int offset = cls.getStaticValuesOffset();
if (offset != 0) {
StaticValuesParser parser = new StaticValuesParser(dex, dex.openSection(offset));
parser.processFields(staticFields);
for (FieldNode f : staticFields) {
AccessInfo accFlags = f.getAccessFlags();
if (accFlags.isStatic() && accFlags.isFinal()) {
FieldValueAttr fv = (FieldValueAttr) f.getAttributes().get(AttributeType.FIELD_VALUE);
if (fv != null && fv.getValue() != null) {
if (accFlags.isPublic()) {
dex.getConstFields().put(fv.getValue(), f);
}
constFields.put(fv.getValue(), f);
}
}
}
}
}
@SuppressWarnings("unchecked")
private void parseClassSignature() {
Annotation a = this.getAttributes().getAnnotation(Consts.DALVIK_SIGNATURE);
if (a == null)
return;
String sign = Utils.mergeSignature((List<String>) a.getDefaultValue());
// parse generic map
int end = Utils.getGenericEnd(sign);
if (end != -1) {
String gen = sign.substring(1, end);
genericMap = ArgType.parseGenericMap(gen);
sign = sign.substring(end + 1);
}
// parse super class signature and interfaces
List<ArgType> list = ArgType.parseSignatureList(sign);
if (list != null && !list.isEmpty()) {
try {
ArgType st = list.remove(0);
this.superClass = ClassInfo.fromType(st);
int i = 0;
for (ArgType it : list) {
ClassInfo interf = ClassInfo.fromType(it);
interfaces.set(i, interf);
i++;
}
} catch (Throwable e) {
LOG.warn("Can't set signatures for class: {}, sign: {}", this, sign, e);
}
}
}
@SuppressWarnings("unchecked")
private void setFieldsTypesFromSignature() {
for (FieldNode field : fields) {
Annotation a = field.getAttributes().getAnnotation(Consts.DALVIK_SIGNATURE);
if (a == null)
continue;
String sign = Utils.mergeSignature((List<String>) a.getDefaultValue());
ArgType gType = ArgType.parseSignature(sign);
if (gType != null)
field.setType(gType);
}
}
@Override
public void load() throws DecodeException {
for (MethodNode mth : getMethods()) {
mth.load();
}
for (ClassNode innerCls : getInnerClasses()) {
innerCls.load();
}
}
@Override
public void unload() {
for (MethodNode mth : getMethods()) {
mth.unload();
}
for (ClassNode innerCls : getInnerClasses()) {
innerCls.unload();
}
}
public ClassInfo getSuperClass() {
return superClass;
}
public List<ClassInfo> getInterfaces() {
return interfaces;
}
public Map<ArgType, List<ArgType>> getGenericMap() {
return genericMap;
}
public List<MethodNode> getMethods() {
return methods;
}
public List<FieldNode> getFields() {
return fields;
}
public FieldNode getConstField(Object obj) {
return getConstField(obj, true);
}
public FieldNode getConstField(Object obj, boolean searchGlobal) {
ClassNode cn = this;
FieldNode field;
do {
field = cn.constFields.get(obj);
}
while (field == null
&& (cn.clsInfo.getParentClass() != null)
&& (cn = dex.resolveClass(cn.clsInfo.getParentClass())) != null);
if (field == null && searchGlobal) {
field = dex.getConstFields().get(obj);
}
return field;
}
public FieldNode getConstFieldByLiteralArg(LiteralArg arg) {
PrimitiveType type = arg.getType().getPrimitiveType();
if (type == null) {
return null;
}
long literal = arg.getLiteral();
switch (type) {
case BOOLEAN:
return getConstField(literal == 1, false);
case CHAR:
return getConstField((char) literal, Math.abs(literal) > 1);
case BYTE:
return getConstField((byte) literal, Math.abs(literal) > 1);
case SHORT:
return getConstField((short) literal, Math.abs(literal) > 1);
case INT:
return getConstField((int) literal, Math.abs(literal) > 1);
case LONG:
return getConstField(literal, Math.abs(literal) > 1);
case FLOAT:
return getConstField(Float.intBitsToFloat((int) literal), true);
case DOUBLE:
return getConstField(Double.longBitsToDouble(literal), true);
}
return null;
}
public FieldNode searchFieldById(int id) {
String name = FieldInfo.getNameById(dex, id);
for (FieldNode f : fields) {
if (f.getName().equals(name))
return f;
}
return null;
}
public FieldNode searchField(FieldInfo field) {
String name = field.getName();
for (FieldNode f : fields) {
if (f.getName().equals(name))
return f;
}
return null;
}
public MethodNode searchMethod(MethodInfo mth) {
for (MethodNode m : methods) {
if (m.getMethodInfo().equals(mth))
return m;
}
return null;
}
public MethodNode searchMethodByName(String shortId) {
for (MethodNode m : methods) {
if (m.getMethodInfo().getShortId().equals(shortId))
return m;
}
return null;
}
public MethodNode searchMethodById(int id) {
return searchMethodByName(MethodInfo.fromDex(dex, id).getShortId());
}
public List<ClassNode> getInnerClasses() {
return innerClasses;
}
public void addInnerClass(ClassNode cls) {
if (innerClasses.isEmpty())
innerClasses = new ArrayList<ClassNode>(3);
innerClasses.add(cls);
}
public boolean isEnum() {
return getAccessFlags().isEnum() && getSuperClass().getFullName().equals(Consts.CLASS_ENUM);
}
public boolean isAnonymous() {
return clsInfo.isInner()
&& getShortName().startsWith(Consts.ANONYMOUS_CLASS_PREFIX)
&& getDefaultConstructor() != null;
}
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()))) {
return mth;
}
}
return null;
}
public AccessInfo getAccessFlags() {
return accessFlags;
}
public DexNode dex() {
return dex;
}
public ClassInfo getClassInfo() {
return clsInfo;
}
public String getShortName() {
return clsInfo.getShortName();
}
public String getFullName() {
return clsInfo.getFullName();
}
public String getPackage() {
return clsInfo.getPackage();
}
public String getRawName() {
return clsInfo.getRawName();
}
public void setCode(CodeWriter code) {
this.code = code;
}
public CodeWriter getCode() {
return code;
}
@Override
public String toString() {
return getFullName();
}
}
@@ -1,13 +1,17 @@
package jadx.dex.nodes;
package jadx.core.dex.nodes;
import jadx.dex.info.ClassInfo;
import jadx.dex.instructions.args.ArgType;
import jadx.utils.exceptions.DecodeException;
import jadx.utils.files.InputFile;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.files.InputFile;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
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;
@@ -29,7 +33,9 @@ public class DexNode {
private final List<ClassNode> classes = new ArrayList<ClassNode>();
private final String[] strings;
public DexNode(RootNode root, InputFile input) throws IOException, DecodeException {
private final Map<Object, FieldNode> constFields = new HashMap<Object, FieldNode>();
public DexNode(RootNode root, InputFile input) {
this.root = root;
this.dexBuf = input.getDexBuffer();
@@ -37,7 +43,7 @@ public class DexNode {
this.strings = stringList.toArray(new String[stringList.size()]);
}
public void loadClasses(RootNode root) throws DecodeException {
public void loadClasses() throws DecodeException {
for (ClassDef cls : dexBuf.classDefs()) {
classes.add(new ClassNode(this, cls));
}
@@ -51,6 +57,26 @@ public class DexNode {
return root.resolveClass(clsInfo);
}
public MethodNode resolveMethod(MethodInfo mth) {
ClassNode cls = resolveClass(mth.getDeclClass());
if (cls != null) {
return cls.searchMethod(mth);
}
return null;
}
public FieldNode resolveField(FieldInfo field) {
ClassNode cls = resolveClass(field.getDeclClass());
if (cls != null) {
return cls.searchField(field);
}
return null;
}
public Map<Object, FieldNode> getConstFields() {
return constFields;
}
// DexBuffer wrappers
public String getString(int index) {
@@ -83,7 +109,7 @@ public class DexNode {
for (short t : paramList.getTypes()) {
args.add(getType(t));
}
return args;
return Collections.unmodifiableList(args);
}
public Code readCode(Method mth) {
@@ -102,5 +128,4 @@ public class DexNode {
public String toString() {
return "DEX";
}
}
@@ -0,0 +1,61 @@
package jadx.core.dex.nodes;
import jadx.core.dex.attributes.LineAttrNode;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.info.AccessInfo.AFType;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.instructions.args.ArgType;
import com.android.dx.io.ClassData.Field;
public class FieldNode extends LineAttrNode {
private final FieldInfo fieldInfo;
private final AccessInfo accFlags;
private ArgType type; // store signature
public FieldNode(ClassNode cls, Field field) {
this.fieldInfo = FieldInfo.fromDex(cls.dex(), field.getFieldIndex());
this.type = fieldInfo.getType();
this.accFlags = new AccessInfo(field.getAccessFlags(), AFType.FIELD);
}
public FieldInfo getFieldInfo() {
return fieldInfo;
}
public AccessInfo getAccessFlags() {
return accFlags;
}
public String getName() {
return fieldInfo.getName();
}
public ArgType getType() {
return type;
}
public void setType(ArgType type) {
this.type = type;
}
@Override
public int hashCode() {
return fieldInfo.hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
FieldNode other = (FieldNode) obj;
return fieldInfo.equals(other.fieldInfo);
}
@Override
public String toString() {
return fieldInfo.getDeclClass() + "." + fieldInfo.getName() + " " + type;
}
}
@@ -1,8 +1,8 @@
package jadx.dex.nodes;
package jadx.core.dex.nodes;
import java.util.List;
public interface IBlock extends IContainer {
public List<InsnNode> getInstructions();
List<InsnNode> getInstructions();
}
@@ -0,0 +1,6 @@
package jadx.core.dex.nodes;
import jadx.core.dex.attributes.IAttributeNode;
public interface IContainer extends IAttributeNode {
}
@@ -0,0 +1,19 @@
package jadx.core.dex.nodes;
import jadx.core.utils.exceptions.DecodeException;
public interface ILoadable {
/**
* On demand loading
*
* @throws DecodeException
*/
void load() throws DecodeException;
/**
* Free resources
*/
void unload();
}
@@ -0,0 +1,11 @@
package jadx.core.dex.nodes;
import java.util.List;
public interface IRegion extends IContainer {
IRegion getParent();
List<IContainer> getSubBlocks();
}
@@ -1,6 +1,6 @@
package jadx.dex.nodes;
package jadx.core.dex.nodes;
import jadx.dex.attributes.AttrNode;
import jadx.core.dex.attributes.AttrNode;
import java.util.List;
@@ -8,7 +8,7 @@ public class InsnContainer extends AttrNode implements IBlock {
private List<InsnNode> insns;
public void setInstructions(List<InsnNode> insns) {
public InsnContainer(List<InsnNode> insns) {
this.insns = insns;
}
@@ -1,13 +1,13 @@
package jadx.dex.nodes;
package jadx.core.dex.nodes;
import jadx.dex.attributes.AttrNode;
import jadx.dex.instructions.InsnType;
import jadx.dex.instructions.args.ArgType;
import jadx.dex.instructions.args.InsnArg;
import jadx.dex.instructions.args.InsnWrapArg;
import jadx.dex.instructions.args.RegisterArg;
import jadx.utils.InsnUtils;
import jadx.utils.Utils;
import jadx.core.dex.attributes.LineAttrNode;
import jadx.core.dex.instructions.InsnType;
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.RegisterArg;
import jadx.core.utils.InsnUtils;
import jadx.core.utils.Utils;
import java.util.ArrayList;
import java.util.Collections;
@@ -15,7 +15,7 @@ import java.util.List;
import com.android.dx.io.instructions.DecodedInstruction;
public class InsnNode extends AttrNode {
public class InsnNode extends LineAttrNode {
protected final InsnType insnType;
@@ -24,11 +24,11 @@ public class InsnNode extends AttrNode {
protected int offset;
protected int insnHashCode = super.hashCode();
protected InsnNode(MethodNode mth, InsnType type) {
this(mth, type, 3);
protected InsnNode(InsnType type) {
this(type, 1);
}
public InsnNode(MethodNode mth, InsnType type, int argsCount) {
public InsnNode(InsnType type, int argsCount) {
this.insnType = type;
this.offset = -1;
@@ -85,11 +85,15 @@ public class InsnNode extends AttrNode {
public boolean replaceArg(InsnArg from, InsnArg to) {
int count = getArgsCount();
for (int i = 0; i < count; i++) {
if (arguments.get(i) == from) {
InsnArg arg = arguments.get(i);
if (arg == from) {
// TODO correct remove from use list
// from.getTypedVar().getUseList().remove(from);
setArg(i, to);
return true;
} else if (arg.isInsnWrap()) {
if (((InsnWrapArg) arg).getWrapInsn().replaceArg(from, to))
return true;
}
}
return false;
@@ -121,13 +125,38 @@ public class InsnNode extends AttrNode {
public void getRegisterArgs(List<RegisterArg> list) {
for (InsnArg arg : this.getArguments()) {
if (arg.isRegister())
if (arg.isRegister()) {
list.add((RegisterArg) arg);
else if (arg.isInsnWrap())
} else if (arg.isInsnWrap()) {
((InsnWrapArg) arg).getWrapInsn().getRegisterArgs(list);
}
}
}
public boolean canReorder() {
switch (getType()) {
case CONST:
case CONST_STR:
case CONST_CLASS:
case CAST:
case MOVE:
case ARITH:
case NEG:
case CMP_L:
case CMP_G:
case CHECK_CAST:
case INSTANCE_OF:
case FILL_ARRAY:
case FILLED_NEW_ARRAY:
case NEW_ARRAY:
case NEW_MULTIDIM_ARRAY:
case STR_CONCAT:
case MOVE_EXCEPTION:
return true;
}
return false;
}
@Override
public String toString() {
return InsnUtils.formatOffset(offset) + ": "
@@ -1,39 +1,46 @@
package jadx.dex.nodes;
package jadx.core.dex.nodes;
import jadx.dex.attributes.AttrNode;
import jadx.dex.attributes.AttributeFlag;
import jadx.dex.attributes.JumpAttribute;
import jadx.dex.info.AccessInfo;
import jadx.dex.info.AccessInfo.AFType;
import jadx.dex.info.ClassInfo;
import jadx.dex.info.MethodInfo;
import jadx.dex.instructions.GotoNode;
import jadx.dex.instructions.IfNode;
import jadx.dex.instructions.InsnDecoder;
import jadx.dex.instructions.SwitchNode;
import jadx.dex.instructions.args.ArgType;
import jadx.dex.instructions.args.InsnArg;
import jadx.dex.instructions.args.RegisterArg;
import jadx.dex.instructions.mods.ConstructorInsn;
import jadx.dex.nodes.parser.DebugInfoParser;
import jadx.dex.trycatch.ExcHandlerAttr;
import jadx.dex.trycatch.ExceptionHandler;
import jadx.dex.trycatch.TryCatchBlock;
import jadx.utils.Utils;
import jadx.utils.exceptions.DecodeException;
import jadx.core.Consts;
import jadx.core.dex.attributes.AttributeFlag;
import jadx.core.dex.attributes.JumpAttribute;
import jadx.core.dex.attributes.LineAttrNode;
import jadx.core.dex.attributes.LoopAttr;
import jadx.core.dex.attributes.annotations.Annotation;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.info.AccessInfo.AFType;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.GotoNode;
import jadx.core.dex.instructions.IfNode;
import jadx.core.dex.instructions.InsnDecoder;
import jadx.core.dex.instructions.SwitchNode;
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.parser.DebugInfoParser;
import jadx.core.dex.trycatch.ExcHandlerAttr;
import jadx.core.dex.trycatch.ExceptionHandler;
import jadx.core.dex.trycatch.TryCatchBlock;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.DecodeException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.android.dx.io.ClassData.Method;
import com.android.dx.io.Code;
import com.android.dx.io.Code.CatchHandler;
import com.android.dx.io.Code.Try;
public class MethodNode extends AttrNode implements ILoadable {
public class MethodNode extends LineAttrNode implements ILoadable {
private static final Logger LOG = LoggerFactory.getLogger(MethodNode.class);
private final MethodInfo mthInfo;
private final ClassNode parentClass;
@@ -44,42 +51,40 @@ public class MethodNode extends AttrNode implements ILoadable {
private List<InsnNode> instructions;
private boolean noCode;
private ArgType retType;
private RegisterArg thisArg;
private List<RegisterArg> argsList;
private Map<ArgType, List<ArgType>> genericMap;
private List<BlockNode> blocks;
private BlockNode enterBlock;
private List<BlockNode> exitBlocks;
private ConstructorInsn superCall;
private IContainer region;
private List<ExceptionHandler> exceptionHandlers;
private List<LoopAttr> loops = Collections.emptyList();
public MethodNode(ClassNode classNode, Method mth) {
this.mthInfo = MethodInfo.fromDex(classNode.dex(), mth.getMethodIndex());
public MethodNode(ClassNode classNode, Method mthData) {
this.mthInfo = MethodInfo.fromDex(classNode.dex(), mthData.getMethodIndex());
this.parentClass = classNode;
this.accFlags = new AccessInfo(mth.getAccessFlags(), AFType.METHOD);
this.methodData = mth;
if (methodData.getCodeOffset() == 0) {
noCode = true;
regsCount = 0;
initArguments();
} else {
noCode = false;
}
this.accFlags = new AccessInfo(mthData.getAccessFlags(), AFType.METHOD);
this.noCode = (mthData.getCodeOffset() == 0);
this.methodData = (noCode ? null : mthData);
}
@Override
public void load() throws DecodeException {
if (noCode)
return;
try {
if (noCode) {
regsCount = 0;
initMethodTypes();
return;
}
DexNode dex = parentClass.dex();
Code mthCode = dex.readCode(methodData);
regsCount = mthCode.getRegistersSize();
initMethodTypes();
InsnDecoder decoder = new InsnDecoder(this, mthCode);
InsnNode[] insnByOffset = decoder.run();
@@ -90,27 +95,39 @@ public class MethodNode extends AttrNode implements ILoadable {
}
((ArrayList<InsnNode>) instructions).trimToSize();
initArguments();
initTryCatches(mthCode, insnByOffset);
initJumps(insnByOffset);
if (mthCode.getDebugInfoOffset() > 0) {
DebugInfoParser debugInfo = new DebugInfoParser(this, dex.openSection(mthCode.getDebugInfoOffset()));
debugInfo.process(insnByOffset);
int debugInfoOffset = mthCode.getDebugInfoOffset();
if (debugInfoOffset > 0) {
DebugInfoParser debugInfoParser = new DebugInfoParser(this, debugInfoOffset, insnByOffset);
debugInfoParser.process();
if (instructions.size() != 0) {
int line = instructions.get(0).getSourceLine();
if (line != 0) {
this.setSourceLine(line - 1);
}
}
}
} catch (Exception e) {
throw new DecodeException(this, "Load method exception", e);
}
}
private void initMethodTypes() {
if (!parseSignature()) {
retType = mthInfo.getReturnType();
initArguments(mthInfo.getArgumentsTypes());
}
}
@Override
public void unload() {
if (noCode)
return;
if (instructions != null) instructions.clear();
// if (blocks != null) blocks.clear();
// if (exitBlocks != null) exitBlocks.clear();
blocks = null;
exitBlocks = null;
if (exceptionHandlers != null) exceptionHandlers.clear();
@@ -118,27 +135,85 @@ public class MethodNode extends AttrNode implements ILoadable {
noCode = true;
}
private void initArguments() {
List<ArgType> args = mthInfo.getArgumentsTypes();
@SuppressWarnings("unchecked")
private boolean parseSignature() {
Annotation a = getAttributes().getAnnotation(Consts.DALVIK_SIGNATURE);
if (a == null)
return false;
String sign = Utils.mergeSignature((List<String>) a.getDefaultValue());
// parse generic map
int end = Utils.getGenericEnd(sign);
if (end != -1) {
String gen = sign.substring(1, end);
genericMap = ArgType.parseGenericMap(gen);
sign = sign.substring(end + 1);
}
int firstBracket = sign.indexOf('(');
int lastBracket = sign.lastIndexOf(')');
String argsTypesStr = sign.substring(firstBracket + 1, lastBracket);
String returnType = sign.substring(lastBracket + 1);
retType = ArgType.parseSignature(returnType);
if (retType == null) {
LOG.warn("Signature parse error: {}", returnType);
return false;
}
List<ArgType> argsTypes = ArgType.parseSignatureList(argsTypesStr);
if (argsTypes == null)
return false;
List<ArgType> mthArgs = mthInfo.getArgumentsTypes();
if (argsTypes.size() != mthArgs.size()) {
if (argsTypes.isEmpty()) {
return false;
}
if (!mthInfo.isConstructor()) {
LOG.warn("Wrong signature parse result: " + sign + " -> " + argsTypes
+ ", not generic version: " + mthArgs);
return false;
} else if (getParentClass().getAccessFlags().isEnum()) {
// TODO:
argsTypes.add(0, mthArgs.get(0));
argsTypes.add(1, mthArgs.get(1));
} else {
// add synthetic arg for outer class
argsTypes.add(0, mthArgs.get(0));
}
if (argsTypes.size() != mthArgs.size()) {
return false;
}
}
initArguments(argsTypes);
return true;
}
private void initArguments(List<ArgType> args) {
int pos;
if (!noCode) {
if (noCode) {
pos = 1;
} else {
pos = regsCount;
for (ArgType arg : args)
pos -= arg.getRegCount();
} else {
pos = 2 * args.size() + 1;
}
if (accFlags.isStatic()) {
thisArg = null;
} else {
thisArg = InsnArg.reg(pos - 1, parentClass.getClassInfo().getType());
thisArg = InsnArg.immutableReg(pos - 1, parentClass.getClassInfo().getType());
thisArg.getTypedVar().setName("this");
}
if (args.isEmpty()) {
argsList = Collections.emptyList();
return;
}
argsList = new ArrayList<RegisterArg>(args.size());
for (ArgType arg : args) {
argsList.add(InsnArg.reg(pos, arg));
argsList.add(InsnArg.immutableReg(pos, arg));
pos += arg.getRegCount();
}
}
@@ -154,22 +229,24 @@ public class MethodNode extends AttrNode implements ILoadable {
}
}
public RegisterArg removeFirstArgument() {
this.getAttributes().add(AttributeFlag.SKIP_FIRST_ARG);
return argsList.remove(0);
}
public RegisterArg getThisArg() {
return thisArg;
}
// TODO: args types can change during type resolving => reset and copy back names
@Deprecated
public void resetArgsTypes() {
List<InsnArg> modArgs = new ArrayList<InsnArg>(argsList);
initArguments();
for (int i = 0; i < argsList.size(); i++) {
argsList.get(i).getTypedVar().setName(modArgs.get(i).getTypedVar().getName());
}
public ArgType getReturnType() {
return retType;
}
// move to external class
public Map<ArgType, List<ArgType>> getGenericMap() {
return genericMap;
}
// TODO: move to external class
private void initTryCatches(Code mthCode, InsnNode[] insnByOffset) {
CatchHandler[] catchBlocks = mthCode.getCatchHandlers();
Try[] tries = mthCode.getTries();
@@ -178,8 +255,8 @@ public class MethodNode extends AttrNode implements ILoadable {
// and we don't need this mapping anymore,
// but in maven repository still old version
Set<Integer> handlerSet = new HashSet<Integer>(tries.length);
for (Try try_ : tries) {
handlerSet.add(try_.getHandlerOffset());
for (Try aTry : tries) {
handlerSet.add(aTry.getHandlerOffset());
}
List<Integer> handlerList = new ArrayList<Integer>(catchBlocks.length);
handlerList.addAll(handlerSet);
@@ -191,17 +268,17 @@ public class MethodNode extends AttrNode implements ILoadable {
Set<Integer> addrs = new HashSet<Integer>();
List<TryCatchBlock> catches = new ArrayList<TryCatchBlock>(catchBlocks.length);
for (CatchHandler catch_ : catchBlocks) {
for (CatchHandler handler : catchBlocks) {
TryCatchBlock tcBlock = new TryCatchBlock();
catches.add(tcBlock);
for (int i = 0; i < catch_.getAddresses().length; i++) {
int addr = catch_.getAddresses()[i];
ClassInfo type = ClassInfo.fromDex(parentClass.dex(), catch_.getTypeIndexes()[i]);
for (int i = 0; i < handler.getAddresses().length; i++) {
int addr = handler.getAddresses()[i];
ClassInfo type = ClassInfo.fromDex(parentClass.dex(), handler.getTypeIndexes()[i]);
tcBlock.addHandler(this, addr, type);
addrs.add(addr);
hc++;
}
int addr = catch_.getCatchAllAddress();
int addr = handler.getCatchAllAddress();
if (addr >= 0) {
tcBlock.addHandler(this, addr, null);
addrs.add(addr);
@@ -232,11 +309,11 @@ public class MethodNode extends AttrNode implements ILoadable {
}
// attach TRY_ENTER, TRY_LEAVE attributes to instructions
for (Try try_ : tries) {
int catchNum = handlerList.indexOf(try_.getHandlerOffset());
for (Try aTry : tries) {
int catchNum = handlerList.indexOf(aTry.getHandlerOffset());
TryCatchBlock block = catches.get(catchNum);
int offset = try_.getStartAddress();
int end = offset + try_.getInstructionCount() - 1;
int offset = aTry.getStartAddress();
int end = offset + aTry.getInstructionCount() - 1;
insnByOffset[offset].getAttributes().add(AttributeFlag.TRY_ENTER);
while (offset <= end && offset >= 0) {
@@ -345,6 +422,25 @@ public class MethodNode extends AttrNode implements ILoadable {
this.exitBlocks.add(exitBlock);
}
public void registerLoop(LoopAttr loop) {
if (loops.isEmpty()) {
loops = new ArrayList<LoopAttr>(5);
}
loops.add(loop);
}
public LoopAttr getLoopForBlock(BlockNode block) {
for (LoopAttr loop : loops) {
if (loop.getLoopBlocks().contains(block))
return loop;
}
return null;
}
public int getLoopsCount() {
return loops.size();
}
public ExceptionHandler addExceptionHandler(ExceptionHandler handler) {
if (exceptionHandlers == null) {
exceptionHandlers = new ArrayList<ExceptionHandler>(2);
@@ -362,6 +458,26 @@ public class MethodNode extends AttrNode implements ILoadable {
return exceptionHandlers;
}
/**
* Return true if exists method with same name and arguments count
*/
public boolean isArgsOverload() {
int argsCount = mthInfo.getArgumentsTypes().size();
if (argsCount == 0) {
return false;
}
String name = getName();
List<MethodNode> methods = parentClass.getMethods();
for (MethodNode method : methods) {
if (this != method
&& method.getName().equals(name)
&& method.mthInfo.getArgumentsTypes().size() == argsCount)
return true;
}
return false;
}
public int getRegsCount() {
return regsCount;
}
@@ -370,14 +486,6 @@ public class MethodNode extends AttrNode implements ILoadable {
return accFlags;
}
public void setSuperCall(ConstructorInsn insn) {
this.superCall = insn;
}
public ConstructorInsn getSuperCall() {
return this.superCall;
}
public IContainer getRegion() {
return region;
}
@@ -394,11 +502,23 @@ public class MethodNode extends AttrNode implements ILoadable {
return mthInfo;
}
@Override
public int hashCode() {
return mthInfo.hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
MethodNode other = (MethodNode) obj;
return mthInfo.equals(other.mthInfo);
}
@Override
public String toString() {
return mthInfo.getReturnType()
return retType
+ " " + parentClass.getFullName() + "." + mthInfo.getName()
+ "(" + Utils.listToString(mthInfo.getArgumentsTypes()) + ")";
}
}
@@ -0,0 +1,100 @@
package jadx.core.dex.nodes;
import jadx.core.clsp.ClspGraph;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.files.InputFile;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class RootNode {
private final Map<String, ClassNode> names = new HashMap<String, ClassNode>();
private List<DexNode> dexNodes;
public void load(List<InputFile> dexFiles) throws DecodeException {
dexNodes = new ArrayList<DexNode>(dexFiles.size());
for (InputFile dex : dexFiles) {
DexNode dexNode;
try {
dexNode = new DexNode(this, dex);
} catch (Exception e) {
throw new DecodeException("Error decode file: " + dex, e);
}
dexNodes.add(dexNode);
}
for (DexNode dexNode : dexNodes) {
dexNode.loadClasses();
}
List<ClassNode> classes = new ArrayList<ClassNode>();
for (DexNode dexNode : dexNodes) {
for (ClassNode cls : dexNode.getClasses()) {
names.put(cls.getFullName(), cls);
}
classes.addAll(dexNode.getClasses());
}
try {
initClassPath(classes);
} catch (IOException e) {
throw new DecodeException("Error loading classpath", e);
}
initInnerClasses(classes);
}
private void initClassPath(List<ClassNode> classes) throws IOException, DecodeException {
ClspGraph clsp = new ClspGraph();
clsp.load();
clsp.addApp(classes);
ArgType.setClsp(clsp);
}
private void initInnerClasses(List<ClassNode> classes) {
// move inner classes
List<ClassNode> inner = new ArrayList<ClassNode>();
for (ClassNode cls : classes) {
if (cls.getClassInfo().isInner())
inner.add(cls);
}
for (ClassNode cls : inner) {
ClassNode parent = resolveClass(cls.getClassInfo().getParentClass());
if (parent == null) {
names.remove(cls.getFullName());
cls.getClassInfo().notInner();
names.put(cls.getFullName(), cls);
} else {
parent.addInnerClass(cls);
}
}
}
public List<ClassNode> getClasses(boolean includeInner) {
List<ClassNode> classes = new ArrayList<ClassNode>();
for (DexNode dexNode : dexNodes) {
for (ClassNode cls : dexNode.getClasses()) {
if (includeInner) {
classes.add(cls);
} else {
if (!cls.getClassInfo().isInner())
classes.add(cls);
}
}
}
return classes;
}
public ClassNode searchClassByName(String fullName) {
return names.get(fullName);
}
public ClassNode resolveClass(ClassInfo cls) {
String fullName = cls.getFullName();
return searchClassByName(fullName);
}
}
@@ -1,17 +1,17 @@
package jadx.dex.nodes.parser;
package jadx.core.dex.nodes.parser;
import jadx.dex.attributes.annotations.Annotation;
import jadx.dex.attributes.annotations.Annotation.Visibility;
import jadx.dex.attributes.annotations.AnnotationsList;
import jadx.dex.attributes.annotations.MethodParameters;
import jadx.dex.nodes.ClassNode;
import jadx.dex.nodes.DexNode;
import jadx.dex.nodes.FieldNode;
import jadx.dex.nodes.MethodNode;
import jadx.utils.exceptions.DecodeException;
import jadx.core.dex.attributes.annotations.Annotation;
import jadx.core.dex.attributes.annotations.Annotation.Visibility;
import jadx.core.dex.attributes.annotations.AnnotationsList;
import jadx.core.dex.attributes.annotations.MethodParameters;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.DexNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.exceptions.DecodeException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -26,27 +26,26 @@ public class AnnotationsParser {
Section section = dex.openSection(offset);
// TODO read as unsigned int
int class_annotations_off = section.readInt();
int fields_size = section.readInt();
int annotated_methods_size = section.readInt();
int annotated_parameters_size = section.readInt();
int classAnnotationsOffset = section.readInt();
int fieldsCount = section.readInt();
int annotatedMethodsCount = section.readInt();
int annotatedParametersCount = section.readInt();
if (class_annotations_off != 0) {
cls.getAttributes().add(readAnnotationSet(class_annotations_off));
if (classAnnotationsOffset != 0) {
cls.getAttributes().add(readAnnotationSet(classAnnotationsOffset));
}
for (int i = 0; i < fields_size; i++) {
for (int i = 0; i < fieldsCount; i++) {
FieldNode f = cls.searchFieldById(section.readInt());
f.getAttributes().add(readAnnotationSet(section.readInt()));
}
for (int i = 0; i < annotated_methods_size; i++) {
for (int i = 0; i < annotatedMethodsCount; i++) {
MethodNode m = cls.searchMethodById(section.readInt());
m.getAttributes().add(readAnnotationSet(section.readInt()));
// LOG.info(m + " " + m.getAttributes());
}
for (int i = 0; i < annotated_parameters_size; i++) {
for (int i = 0; i < annotatedParametersCount; i++) {
MethodNode mth = cls.searchMethodById(section.readInt());
// read annotation ref list
Section ss = dex.openSection(section.readInt());
@@ -73,24 +72,24 @@ public class AnnotationsParser {
return new AnnotationsList(list);
}
private static final Annotation.Visibility[] visibilities = new Annotation.Visibility[] {
private static final Annotation.Visibility[] VISIBILITIES = new Annotation.Visibility[]{
Annotation.Visibility.BUILD,
Annotation.Visibility.RUNTIME,
Annotation.Visibility.SYSTEM
};
public static Annotation readAnnotation(DexNode dex, Section s, boolean readVisibility) throws DecodeException {
EncValueParser ep = new EncValueParser(dex, s);
EncValueParser parser = new EncValueParser(dex, s);
Visibility visibility = null;
if (readVisibility)
visibility = visibilities[s.readByte()];
if (readVisibility) {
visibility = VISIBILITIES[s.readByte()];
}
int typeIndex = s.readUleb128();
int size = s.readUleb128();
Map<String, Object> values = new HashMap<String, Object>(size);
Map<String, Object> values = new LinkedHashMap<String, Object>(size);
for (int i = 0; i < size; i++) {
String name = dex.getString(s.readUleb128());
values.put(name, ep.parseValue());
values.put(name, parser.parseValue());
}
return new Annotation(visibility, dex.getType(typeIndex), values);
}
@@ -0,0 +1,219 @@
package jadx.core.dex.nodes.parser;
import jadx.core.dex.attributes.SourceFileAttr;
import jadx.core.dex.instructions.args.InsnArg;
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.exceptions.DecodeException;
import java.util.List;
import com.android.dx.io.DexBuffer.Section;
public class DebugInfoParser {
private static final int DBG_END_SEQUENCE = 0x00;
private static final int DBG_ADVANCE_PC = 0x01;
private static final int DBG_ADVANCE_LINE = 0x02;
private static final int DBG_START_LOCAL = 0x03;
private static final int DBG_START_LOCAL_EXTENDED = 0x04;
private static final int DBG_END_LOCAL = 0x05;
private static final int DBG_RESTART_LOCAL = 0x06;
private static final int DBG_SET_PROLOGUE_END = 0x07;
private static final int DBG_SET_EPILOGUE_BEGIN = 0x08;
private static final int DBG_SET_FILE = 0x09;
private static final int DBG_FIRST_SPECIAL = 0x0a; // the smallest special opcode
private static final int DBG_LINE_BASE = -4; // the smallest line number increment
private static final int DBG_LINE_RANGE = 15; // the number of line increments represented
private final MethodNode mth;
private final Section section;
private final DexNode dex;
private final LocalVar[] locals;
private final InsnArg[] activeRegisters;
private final InsnNode[] insnByOffset;
public DebugInfoParser(MethodNode mth, int debugOffset, InsnNode[] insnByOffset) {
this.mth = mth;
this.dex = mth.dex();
this.section = dex.openSection(debugOffset);
this.locals = new LocalVar[mth.getRegsCount()];
this.activeRegisters = new InsnArg[mth.getRegsCount()];
this.insnByOffset = insnByOffset;
}
public void process() throws DecodeException {
int addr = 0;
int line = section.readUleb128();
int paramsCount = section.readUleb128(); // exclude 'this'
List<RegisterArg> mthArgs = mth.getArguments(false);
assert paramsCount == mthArgs.size();
for (int i = 0; i < paramsCount; i++) {
int id = section.readUleb128() - 1;
if (id != DexNode.NO_INDEX) {
String name = dex.getString(id);
mthArgs.get(i).getTypedVar().setName(name);
}
}
for (RegisterArg arg : mthArgs) {
int rn = arg.getRegNum();
locals[rn] = new LocalVar(arg);
activeRegisters[rn] = arg;
}
addrChange(-1, 1, line); // process '0' instruction
int c = section.readByte() & 0xFF;
while (c != DBG_END_SEQUENCE) {
switch (c) {
case DBG_ADVANCE_PC: {
int addrInc = section.readUleb128();
addr = addrChange(addr, addrInc, line);
break;
}
case DBG_ADVANCE_LINE: {
line += section.readSleb128();
break;
}
case DBG_START_LOCAL: {
int regNum = section.readUleb128();
int nameId = section.readUleb128() - 1;
int type = section.readUleb128() - 1;
LocalVar var = new LocalVar(dex, regNum, nameId, type, DexNode.NO_INDEX);
startVar(var, addr, line);
break;
}
case DBG_START_LOCAL_EXTENDED: {
int regNum = section.readUleb128();
int nameId = section.readUleb128() - 1;
int type = section.readUleb128() - 1;
int sign = section.readUleb128() - 1;
LocalVar var = new LocalVar(dex, regNum, nameId, type, sign);
startVar(var, addr, line);
break;
}
case DBG_RESTART_LOCAL: {
int regNum = section.readUleb128();
LocalVar var = locals[regNum];
if (var != null) {
var.end(addr, line);
setVar(var);
var.start(addr, line);
}
break;
}
case DBG_END_LOCAL: {
int regNum = section.readUleb128();
LocalVar var = locals[regNum];
if (var != null) {
var.end(addr, line);
setVar(var);
}
break;
}
case DBG_SET_PROLOGUE_END:
case DBG_SET_EPILOGUE_BEGIN:
// do nothing
break;
case DBG_SET_FILE: {
int idx = section.readUleb128() - 1;
if (idx != DexNode.NO_INDEX) {
String sourceFile = dex.getString(idx);
mth.getAttributes().add(new SourceFileAttr(sourceFile));
}
break;
}
default: {
if (c >= DBG_FIRST_SPECIAL) {
int adjustedOpcode = c - DBG_FIRST_SPECIAL;
line += DBG_LINE_BASE + (adjustedOpcode % DBG_LINE_RANGE);
int addrInc = (adjustedOpcode / DBG_LINE_RANGE);
addr = addrChange(addr, addrInc, line);
} else {
throw new DecodeException("Unknown debug insn code: " + c);
}
break;
}
}
c = section.readByte() & 0xFF;
}
for (LocalVar var : locals) {
if (var != null && !var.isEnd()) {
var.end(addr, line);
setVar(var);
}
}
}
private int addrChange(int addr, int addrInc, int line) {
int newAddr = addr + addrInc;
for (int i = addr + 1; i <= newAddr; i++) {
InsnNode insn = insnByOffset[i];
if (insn == null)
continue;
insn.setSourceLine(line);
for (InsnArg arg : insn.getArguments())
if (arg.isRegister()) {
activeRegisters[((RegisterArg) arg).getRegNum()] = arg;
}
RegisterArg res = insn.getResult();
if (res != null)
activeRegisters[res.getRegNum()] = res;
}
return newAddr;
}
private void startVar(LocalVar var, int addr, int line) {
int regNum = var.getRegNum();
LocalVar prev = locals[regNum];
if (prev != null && !prev.isEnd()) {
prev.end(addr, line);
setVar(prev);
}
var.start(addr, line);
locals[regNum] = var;
}
private void setVar(LocalVar var) {
int start = var.getStartAddr();
int end = var.getEndAddr();
for (int i = start; i <= end; i++) {
InsnNode insn = insnByOffset[i];
if (insn != null)
fillLocals(insn, var);
}
merge(activeRegisters[var.getRegNum()], var);
}
private static void fillLocals(InsnNode insn, LocalVar var) {
if (insn.getResult() != null)
merge(insn.getResult(), var);
for (InsnArg arg : insn.getArguments())
merge(arg, var);
}
private static void merge(InsnArg arg, LocalVar var) {
if (arg != null && arg.isRegister()) {
if (var.getRegNum() == ((RegisterArg) arg).getRegNum()) {
arg.mergeDebugInfo(var);
}
}
}
}
@@ -0,0 +1,112 @@
package jadx.core.dex.nodes.parser;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.nodes.DexNode;
import jadx.core.utils.exceptions.DecodeException;
import java.util.ArrayList;
import java.util.List;
import com.android.dx.io.DexBuffer.Section;
import com.android.dx.io.EncodedValueReader;
import com.android.dx.util.Leb128Utils;
public class EncValueParser extends EncodedValueReader {
private final DexNode dex;
public EncValueParser(DexNode dex, Section in) {
super(in);
this.dex = dex;
}
public Object parseValue() throws DecodeException {
int argAndType = readByte();
int type = argAndType & 0x1F;
int arg = (argAndType & 0xE0) >> 5;
int size = arg + 1;
switch (type) {
case ENCODED_NULL:
return null;
case ENCODED_BOOLEAN:
return arg == 1;
case ENCODED_BYTE:
return in.readByte();
case ENCODED_SHORT:
return (short) parseNumber(size, true);
case ENCODED_CHAR:
return (char) parseUnsignedInt(size);
case ENCODED_INT:
return (int) parseNumber(size, true);
case ENCODED_LONG:
return parseNumber(size, true);
case ENCODED_FLOAT:
return Float.intBitsToFloat((int) parseNumber(size, false, 4));
case ENCODED_DOUBLE:
return Double.longBitsToDouble(parseNumber(size, false, 8));
case ENCODED_STRING:
return dex.getString(parseUnsignedInt(size));
case ENCODED_TYPE:
return dex.getType(parseUnsignedInt(size));
case ENCODED_METHOD:
return MethodInfo.fromDex(dex, parseUnsignedInt(size));
case ENCODED_FIELD:
case ENCODED_ENUM:
return FieldInfo.fromDex(dex, parseUnsignedInt(size));
case ENCODED_ARRAY:
int count = Leb128Utils.readUnsignedLeb128(in);
List<Object> values = new ArrayList<Object>(count);
for (int i = 0; i < count; i++) {
values.add(parseValue());
}
return values;
case ENCODED_ANNOTATION:
return AnnotationsParser.readAnnotation(dex, (Section) in, false);
}
throw new DecodeException("Unknown encoded value type: 0x" + Integer.toHexString(type));
}
private int parseUnsignedInt(int byteCount) {
return (int) parseNumber(byteCount, false, 0);
}
private long parseNumber(int byteCount, boolean isSignExtended) {
return parseNumber(byteCount, isSignExtended, 0);
}
private long parseNumber(int byteCount, boolean isSignExtended, int fillOnRight) {
long result = 0;
long last = 0;
for (int i = 0; i < byteCount; i++) {
last = readByte();
result |= last << i * 8;
}
if (fillOnRight != 0) {
for (int i = byteCount; i < fillOnRight; i++) {
result <<= 8;
}
} else {
if (isSignExtended && (last & 0x80) != 0) {
for (int i = byteCount; i < 8; i++) {
result |= (long) 0xFF << i * 8;
}
}
}
return result;
}
private int readByte() {
return in.readByte() & 0xFF;
}
}

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