Compare commits

...

140 Commits

Author SHA1 Message Date
Skylot 18a1788d2d gui: fix class members expand 2014-03-25 23:04:00 +04:00
Skylot d0aa19118b core: fix comodification exception 2014-03-25 21:21:30 +04:00
Skylot 039f6eebda core: rename depth traversal class 2014-03-25 21:20:47 +04:00
Skylot 8a464e8274 core: fix condition processing errors 2014-03-23 23:00:25 +04:00
Skylot 066b5a895d core: refactor 'if' regions processing 2014-03-22 17:19:01 +04:00
Skylot 4c4af7928e core: fix dot graph dump 2014-03-19 21:42:10 +04:00
Skylot a0d8d9fcc6 core: fix 'break' detection in loops 2014-03-17 23:39:33 +04:00
Skylot a2142b2ff8 gui: add tabbed pane menu 2014-03-16 16:47:38 +04:00
Skylot 5ed5ec5f7d gui: refactor UI classes 2014-03-16 13:16:19 +04:00
Skylot 95795620d5 core: fix indent for 'break' in 'case' block, refactor tests 2014-03-11 23:56:28 +04:00
Skylot 890c0a9909 refactor: remove deprecated methods 2014-03-10 23:00:14 +04:00
Skylot b73cb40690 core: refactor DotGraphVisitor 2014-03-10 16:19:24 +04:00
Skylot ca448fc4d8 core: fix variable definitions for 'try' blocks 2014-03-10 15:28:12 +04:00
Skylot 7a51c0d087 core: fix signature processing for local variables 2014-03-10 15:06:03 +04:00
Skylot 8762125bbf remove logback from jadx-core dependencies 2014-03-10 12:20:26 +04:00
Skylot 3d0c6e49ab core: fix inline in 'move' instruction 2014-03-10 11:36:20 +04:00
Skylot 03da35b29e core: fix wildcard signature processing 2014-03-09 23:06:17 +04:00
Skylot 3ccab60f43 core: remove redundant parenthesis for arithmetic operations 2014-03-09 19:13:49 +04:00
Skylot ed64b8c121 gui: add hyperlinks for jump to definitions 2014-03-09 17:48:04 +04:00
Skylot 2a60ac47fe core: annotate generated code with reference to used methods 2014-03-09 17:14:58 +04:00
Skylot 9cd72fe1e9 update dependencies version 2014-03-09 17:02:19 +04:00
Skylot 476b2c3735 core: fix inner class handling in classpath and signature parser 2014-03-04 23:37:42 +04:00
Skylot 5258c8363a core: fix NPE in loops processing 2014-03-04 23:32:52 +04:00
Skylot eb6d145dca core: fix indent for anonymous classes 2014-03-03 22:36:38 +04:00
Skylot 63c003a02d core: fix generic types for local variables 2014-03-02 23:34:56 +04:00
Skylot 5557fd814b fix some code style issues 2014-03-02 23:34:40 +04:00
Skylot b1dc26ee06 core: fix missing imports for anonymous classes 2014-03-02 22:30:26 +04:00
Skylot 56c0a588de core: fix imports for inner classes with same names 2014-03-02 16:30:11 +04:00
Skylot 47d65fcd87 core: improve signature parser 2014-03-01 22:38:18 +04:00
Skylot 85ab095630 core: fix class imports 2014-02-26 23:01:00 +04:00
Skylot 1b5f0f6af6 core: move tests 2014-02-26 22:58:22 +04:00
Skylot 2cf28eb2e7 core: fix loop detection 2014-02-25 23:53:30 +04:00
Skylot 2b300341a0 core: improve error reporting for inconsistent code 2014-02-25 23:52:05 +04:00
Skylot 01fabca358 core: fix 'this' reference in anonymous classes 2014-02-25 22:30:27 +04:00
Skylot 4ace552a27 core: fix duplicate cast 2014-02-23 01:19:46 +04:00
Skylot b61daaed33 core: fix synchronized block processing 2014-02-22 23:22:59 +04:00
Skylot c6f0c89cf6 core: fix indent for anonymous class 2014-02-22 18:54:51 +04:00
Skylot 3c84975a09 fix code style issues 2014-02-22 18:54:51 +04:00
Skylot bb4ef4f0a2 core: simplify conditions 2014-02-22 18:54:51 +04:00
Skylot fd00330e6e update gradle to 1.11 2014-02-22 18:54:41 +04:00
Skylot d10efec1ab core: fix type for one time used args 2014-02-13 23:08:24 +04:00
Skylot 3f08c99f19 core: use ternary operator 2014-01-03 23:48:40 +04:00
Skylot e3606d1b53 reformat code (force braces) 2014-01-03 23:30:30 +04:00
Skylot ab593e3cd9 refactor some classes 2014-01-03 22:54:31 +04:00
Skylot 4a0aacf104 gui: fix inner classes opening 2013-12-28 19:35:03 +04:00
Skylot 917cf20d37 core: fix decompiled lines info 2013-12-28 19:33:52 +04:00
Skylot dabaeed8df core: add return type to method short id 2013-12-28 15:58:25 +04:00
Skylot 4923b36e70 core: move instruction remover class to utils 2013-12-28 15:53:25 +04:00
Skylot ebf06fde65 gui: remove not generated elements from class node tree 2013-12-27 23:30:20 +04:00
Skylot 438b3b50d9 gui: fix missed nodes in hierarchical packages tree 2013-12-26 23:33:38 +04:00
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
366 changed files with 17367 additions and 7157 deletions
+5 -1
View File
@@ -7,15 +7,19 @@
.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/)
+28 -20
View File
@@ -1,42 +1,50 @@
## JADX [![Build Status](https://buildhive.cloudbees.com/job/skylot/job/jadx/badge/icon)](https://buildhive.cloudbees.com/job/skylot/job/jadx/)
## JADX
**jadx** - Dex to Java decompiler
Command line tool for produce Java sources from Android Dex and Jar files
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-cli)
- [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
### 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`)
Scripts for run jadx will be placed in `build/install/jadx/bin`
and also packed to `build/distributions/jadx-<version>.zip`
Scripts for run jadx will be placed in `build/jadx/bin`
and also packed to `build/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 or .jar)
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)
--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
```
+55 -76
View File
@@ -1,94 +1,73 @@
apply plugin: 'java'
apply plugin: 'application'
ext.jadxVersion = file('version').readLines().get(0)
version = jadxVersion
apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: 'sonar-runner'
sourceCompatibility = 1.6
targetCompatibility = 1.6
subprojects {
apply plugin: 'java'
apply plugin: 'jacoco'
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.7.5'
compile 'ch.qos.logback:logback-classic:1.0.13'
testCompile 'junit:junit:4.11'
}
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.6'
testCompile 'junit:junit:4.11'
testCompile 'org.mockito:mockito-core:1.9.5'
testCompile 'ch.qos.logback:logback-classic:1.1.1'
}
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.6'
gradleVersion = '1.11'
}
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.6-bin.zip
distributionUrl=http\://services.gradle.org/distributions/gradle-1.11-all.zip
+29
View File
@@ -0,0 +1,29 @@
apply plugin: 'application'
mainClassName = 'jadx.cli.JadxCLI'
applicationName = 'jadx'
dependencies {
compile(project(':jadx-core'))
compile 'com.beust:jcommander:1.30'
compile 'ch.qos.logback:logback-classic:1.1.1'
}
startScripts {
doLast {
// increase default max heap size
String var = 'DEFAULT_JVM_OPTS='
String args = '-Xmx1300M'
unixScript.text = unixScript.text.replace(var + '""', var + '"' + args + '"')
windowsScript.text = windowsScript.text.replace(var, var + args)
}
}
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);
}
}
}
@@ -1,10 +1,10 @@
package jadx;
package jadx.cli;
import jadx.utils.exceptions.JadxException;
import jadx.utils.files.InputFile;
import jadx.api.IJadxArgs;
import jadx.core.Consts;
import jadx.core.utils.exceptions.JadxException;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.lang.reflect.Field;
import java.util.ArrayList;
@@ -18,10 +18,9 @@ import com.beust.jcommander.Parameter;
import com.beust.jcommander.ParameterDescription;
import com.beust.jcommander.ParameterException;
public class JadxArgs implements IJadxArgs {
private static final Logger LOG = LoggerFactory.getLogger(JadxArgs.class);
public final class JadxCLIArgs implements IJadxArgs {
@Parameter(description = "<input files> (.dex, .apk, .jar or .class)", required = true)
@Parameter(description = "<input file> (.dex, .apk or .jar)")
protected List<String> files;
@Parameter(names = {"-d", "--output-dir"}, description = "output directory")
@@ -33,7 +32,7 @@ public class JadxArgs implements IJadxArgs {
@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")
@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)")
@@ -45,67 +44,66 @@ public class JadxArgs implements IJadxArgs {
@Parameter(names = {"-h", "--help"}, description = "print this help", help = true)
protected boolean printHelp = false;
private final List<InputFile> input = new ArrayList<InputFile>();
private final List<File> input = new ArrayList<File>(1);
private File outputDir;
public void parse(String[] args) {
public JadxCLIArgs(String[] args) {
parse(args);
processArgs();
}
private void parse(String[] args) {
try {
new JCommander(this, args);
} catch (ParameterException e) {
System.out.println("Arguments parse error: " + e.getMessage());
System.out.println();
printHelp = true;
System.err.println("Arguments parse error: " + e.getMessage());
printUsage();
System.exit(1);
}
}
public void processArgs() throws JadxException {
if (printHelp)
return;
if (files == null || files.isEmpty())
throw new JadxException("Please specify at least one input file");
for (String fileName : files) {
File file = new File(fileName);
if (!file.exists())
throw new JadxException("File not found: " + file);
try {
input.add(new InputFile(file));
} catch (IOException e) {
throw new JadxException("File processing error: " + file, e);
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);
}
if (input.isEmpty())
throw new JadxException("No files with correct extension (must be '.dex', '.class' or '.jar')");
if (threadsCount <= 0)
throw new JadxException("Threads count must be positive");
if (outDirName == null) {
File file = new File(files.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);
if (!outputDir.exists() && !outputDir.mkdirs())
throw new JadxException("Can't create directory " + outputDir);
if (!outputDir.isDirectory())
throw new JadxException("Output file exists as file " + outputDir);
}
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());
@@ -116,20 +114,20 @@ public class JadxArgs implements IJadxArgs {
int maxNamesLen = 0;
for (ParameterDescription p : params) {
int len = p.getNames().length();
if (len > maxNamesLen)
if (len > maxNamesLen) {
maxNamesLen = len;
}
}
Field[] fields = this.getClass().getDeclaredFields();
for (Field f : fields) {
for (ParameterDescription p : params) {
if (f.getName().equals(p.getParameterized().getName())) {
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());
if (p.getParameter().required())
opt.append(" [required]");
out.println(opt.toString());
break;
}
@@ -140,15 +138,27 @@ public class JadxArgs implements IJadxArgs {
}
private static void addSpaces(StringBuilder str, int count) {
for (int i = 0; i < count; i++)
for (int i = 0; i < count; i++) {
str.append(' ');
}
}
public List<File> getInput() {
return input;
}
@Override
public File getOutDir() {
return outputDir;
}
public void setOutputDir(File outputDir) {
this.outputDir = outputDir;
}
public boolean isPrintHelp() {
return printHelp;
}
@Override
public int getThreadsCount() {
return threadsCount;
@@ -164,11 +174,6 @@ public class JadxArgs implements IJadxArgs {
return rawCfgOutput;
}
@Override
public List<InputFile> getInput() {
return input;
}
@Override
public boolean isFallbackMode() {
return fallbackMode;
@@ -178,10 +183,4 @@ public class JadxArgs implements IJadxArgs {
public boolean isVerbose() {
return verbose;
}
@Override
public boolean isPrintHelp() {
return printHelp;
}
}
+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>
+8
View File
@@ -0,0 +1,8 @@
ext.jadxClasspath = 'clsp-data/android-4.3.jar'
dependencies {
compile 'com.google.android.tools:dx:1.7'
runtime files(jadxClasspath)
}
Binary file not shown.
@@ -0,0 +1,54 @@
package jadx.api;
public final class CodePosition {
private final JavaClass cls;
private final int line;
private final int offset;
public CodePosition(JavaClass cls, int line, int offset) {
this.cls = cls;
this.line = line;
this.offset = offset;
}
public CodePosition(int line, int offset) {
this.cls = null;
this.line = line;
this.offset = offset;
}
public JavaClass getJavaClass() {
return cls;
}
public int getLine() {
return line;
}
public int getOffset() {
return offset;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
CodePosition that = (CodePosition) o;
return line == that.line && offset == that.offset;
}
@Override
public int hashCode() {
return line + 31 * offset;
}
@Override
public String toString() {
return line + ":" + offset + (cls != null ? " " + cls : "");
}
}
@@ -0,0 +1,206 @@
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.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.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;
private List<JavaClass> classes;
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(Collections.singletonList(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.shutdown();
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);
final List<IDexTreeVisitor> passList = new ArrayList<IDexTreeVisitor>(passes);
SaveCode savePass = new SaveCode(outDir, args);
passList.add(savePass);
LOG.info("processing ...");
ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(threadsCount);
for (final ClassNode cls : root.getClasses(false)) {
executor.execute(new Runnable() {
@Override
public void run() {
ProcessClass.process(cls, passList);
}
});
}
return executor;
}
public List<JavaClass> getClasses() {
if (classes == null) {
List<ClassNode> classNodeList = root.getClasses(false);
List<JavaClass> clsList = new ArrayList<JavaClass>(classNodeList.size());
for (ClassNode classNode : classNodeList) {
clsList.add(new JavaClass(this, classNode));
}
classes = Collections.unmodifiableList(clsList);
}
return classes;
}
public List<JavaPackage> getPackages() {
Map<String, List<JavaClass>> map = new HashMap<String, List<JavaClass>>();
for (JavaClass javaClass : getClasses()) {
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 {
reset();
root = new RootNode();
LOG.info("loading ...");
root.load(inputFiles);
}
private void reset() {
ClassInfo.clearCache();
ErrorsCounter.reset();
classes = null;
}
void processClass(ClassNode cls) {
LOG.info("processing class {} ...", cls);
ProcessClass.process(cls, passes);
}
RootNode getRoot() {
return root;
}
JavaClass findJavaClass(ClassNode cls) {
if (cls == null) {
return null;
}
for (JavaClass javaClass : getClasses()) {
if (javaClass.getClassNode().equals(cls)) {
return javaClass;
}
}
return null;
}
}
@@ -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,176 @@
package jadx.api;
import jadx.core.codegen.CodeWriter;
import jadx.core.dex.attributes.AttributeFlag;
import jadx.core.dex.attributes.LineAttrNode;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.exceptions.JadxRuntimeException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
public final class JavaClass {
private final Decompiler decompiler;
private final ClassNode cls;
private List<JavaClass> innerClasses = Collections.emptyList();
private List<JavaField> fields = Collections.emptyList();
private List<JavaMethod> methods = Collections.emptyList();
JavaClass(Decompiler decompiler, ClassNode classNode) {
this.decompiler = decompiler;
this.cls = classNode;
}
public String getCode() {
CodeWriter code = cls.getCode();
if (code == null) {
decompile();
code = cls.getCode();
}
return code != null ? code.toString() : "error processing class";
}
public void decompile() {
if (decompiler == null) {
throw new JadxRuntimeException("Can't decompile inner class");
}
if (cls.getCode() == null) {
decompiler.processClass(cls);
load();
}
}
ClassNode getClassNode() {
return cls;
}
private void load() {
int inClsCount = cls.getInnerClasses().size();
if (inClsCount != 0) {
List<JavaClass> list = new ArrayList<JavaClass>(inClsCount);
for (ClassNode inner : cls.getInnerClasses()) {
if (!inner.getAttributes().contains(AttributeFlag.DONT_GENERATE)) {
JavaClass javaClass = new JavaClass(null, inner);
javaClass.load();
list.add(javaClass);
}
}
this.innerClasses = Collections.unmodifiableList(list);
}
int fieldsCount = cls.getFields().size();
if (fieldsCount != 0) {
List<JavaField> flds = new ArrayList<JavaField>(fieldsCount);
for (FieldNode f : cls.getFields()) {
if (!f.getAttributes().contains(AttributeFlag.DONT_GENERATE)) {
flds.add(new JavaField(f));
}
}
this.fields = Collections.unmodifiableList(flds);
}
int methodsCount = cls.getMethods().size();
if (methodsCount != 0) {
List<JavaMethod> mths = new ArrayList<JavaMethod>(methodsCount);
for (MethodNode m : cls.getMethods()) {
if (!m.getAttributes().contains(AttributeFlag.DONT_GENERATE)) {
mths.add(new JavaMethod(this, 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);
}
}
private Map<CodePosition, Object> getCodeAnnotations() {
decompile();
return cls.getCode().getAnnotations();
}
public CodePosition getDefinitionPosition(int line, int offset) {
Map<CodePosition, Object> map = getCodeAnnotations();
Object obj = map.get(new CodePosition(line, offset));
if (obj instanceof LineAttrNode) {
ClassNode clsNode = null;
if (obj instanceof ClassNode) {
clsNode = (ClassNode) obj;
} else if (obj instanceof MethodNode) {
clsNode = ((MethodNode) obj).getParentClass();
} else if (obj instanceof FieldNode) {
clsNode = ((FieldNode) obj).getParentClass();
}
if (clsNode == null) {
return null;
}
clsNode = clsNode.getParentClass();
JavaClass jCls = decompiler.findJavaClass(clsNode);
if (jCls == null) {
return null;
}
jCls.decompile();
int defLine = ((LineAttrNode) obj).getDecompiledLine();
return new CodePosition(jCls, defLine, 0);
}
return null;
}
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;
}
public int getDecompiledLine() {
return cls.getDecompiledLine();
}
@Override
public boolean equals(Object o) {
return this == o || o instanceof JavaClass && cls.equals(((JavaClass) o).cls);
}
@Override
public int hashCode() {
return cls.hashCode();
}
@Override
public String toString() {
return getFullName();
}
}
@@ -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,49 @@
package jadx.api;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.MethodNode;
import java.util.List;
public final class JavaMethod {
private final MethodNode mth;
private final JavaClass parent;
public JavaMethod(JavaClass cls, MethodNode m) {
this.parent = cls;
this.mth = m;
}
public String getName() {
return mth.getMethodInfo().getName();
}
public JavaClass getDeclaringClass() {
return parent;
}
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,48 @@
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;
return name.equals(that.name);
}
@Override
public int hashCode() {
return name.hashCode();
}
@Override
public String toString() {
return name;
}
}
@@ -1,9 +1,7 @@
package jadx;
import jadx.utils.Utils;
package jadx.core;
public class Consts {
public static final String JADX_VERSION = Utils.getJadxVersion();
public static final String JADX_VERSION = Jadx.getVersion();
public static final boolean DEBUG = false;
@@ -22,4 +20,7 @@ public class Consts {
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_";
public static final String MTH_TOSTRING_SIGNATURE = "toString()Ljava/lang/String;";
}
+107
View File
@@ -0,0 +1,107 @@
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.MethodInlineVisitor;
import jadx.core.dex.visitors.ModVisitor;
import jadx.core.dex.visitors.PrepareForCodeGen;
import jadx.core.dex.visitors.SimplifyVisitor;
import jadx.core.dex.visitors.regions.CheckRegions;
import jadx.core.dex.visitors.regions.IfRegionVisitor;
import jadx.core.dex.visitors.regions.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 CodeShrinker());
passes.add(new RegionMakerVisitor());
passes.add(new IfRegionVisitor());
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 MethodInlineVisitor());
passes.add(new ClassModifier());
passes.add(new PrepareForCodeGen());
}
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";
}
}
@@ -0,0 +1,33 @@
package jadx.core;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.visitors.DepthTraversal;
import jadx.core.dex.visitors.IDexTreeVisitor;
import jadx.core.utils.exceptions.DecodeException;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public final class ProcessClass {
private static final Logger LOG = LoggerFactory.getLogger(ProcessClass.class);
private ProcessClass() {
}
public static void process(ClassNode cls, List<IDexTreeVisitor> passes) {
try {
cls.load();
for (IDexTreeVisitor visitor : passes) {
DepthTraversal.visit(visitor, cls);
}
} catch (DecodeException e) {
LOG.error("Decode exception: " + cls, e);
} catch (Exception e) {
LOG.error("Class process exception: " + cls, e);
} finally {
cls.unload();
}
}
}
@@ -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,131 @@
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.Collections;
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();
for (ClassNode cls : classes) {
size += cls.getInnerClasses().size();
}
NClass[] nClasses = new NClass[size];
int k = 0;
for (ClassNode cls : classes) {
nClasses[k++] = addClass(cls);
for (ClassNode inner : cls.getInnerClasses()) {
nClasses[k++] = addClass(inner);
}
}
for (int i = 0; i < size; i++) {
nClasses[i].setParents(ClsSet.makeParentsArray(classes.get(i), nameMap));
}
}
private NClass addClass(ClassNode cls) {
NClass nClass = new NClass(cls.getRawName(), -1);
nameMap.put(cls.getRawName(), nClass);
return nClass;
}
public boolean isImplements(String clsName, String implClsName) {
Set<String> anc = getAncestors(clsName);
return anc.contains(implClsName);
}
public String getCommonAncestor(String clsName, String implClsName) {
if (clsName.equals(implClsName)) {
return clsName;
}
NClass cls = nameMap.get(implClsName);
if (cls != null) {
if (isImplements(clsName, implClsName)) {
return implClsName;
}
Set<String> anc = getAncestors(clsName);
return searchCommonParent(anc, cls);
}
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) {
return result;
}
NClass cls = nameMap.get(clsName);
if (cls != null) {
result = new HashSet<String>();
addAncestorsNames(cls, result);
if (result.isEmpty()) {
result = Collections.emptySet();
}
ancestorCache.put(clsName, result);
return result;
}
LOG.debug("Missing class: {}", clsName);
return Collections.emptySet();
}
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,58 @@
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;
return name.equals(nClass.name);
}
@Override
public String toString() {
return name;
}
}
@@ -0,0 +1,170 @@
package jadx.core.codegen;
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;
import java.util.Map;
import java.util.Map.Entry;
public class AnnotationGen {
private final ClassNode cls;
private final ClassGen classGen;
public AnnotationGen(ClassNode cls, ClassGen classGen) {
this.cls = cls;
this.classGen = classGen;
}
public void addForClass(CodeWriter code) {
add(cls, code);
}
public void addForMethod(CodeWriter code, MethodNode mth) {
add(mth, code);
}
public void addForField(CodeWriter code, FieldNode field) {
add(field, code);
}
public void addForParameter(CodeWriter code, MethodParameters paramsAnnotations, int n) {
AnnotationsList aList = paramsAnnotations.getParamList().get(n);
if (aList == null || aList.size() == 0) {
return;
}
for (Annotation a : aList.getAll()) {
formatAnnotation(code, a);
code.add(' ');
}
}
private void add(IAttributeNode node, CodeWriter code) {
AnnotationsList aList = (AnnotationsList) node.getAttributes().get(AttributeType.ANNOTATION_LIST);
if (aList == null || aList.size() == 0) {
return;
}
for (Annotation a : aList.getAll()) {
String aCls = a.getAnnotationClass();
if (aCls.startsWith(Consts.DALVIK_ANNOTATION_PKG)) {
// skip
if (Consts.DEBUG) {
code.startLine("// " + a);
}
} else {
code.startLine();
formatAnnotation(code, a);
}
}
}
private void formatAnnotation(CodeWriter code, Annotation a) {
code.add('@');
code.add(classGen.useClass(a.getType()));
Map<String, Object> vl = a.getValues();
if (!vl.isEmpty()) {
code.add('(');
if (vl.size() == 1 && vl.containsKey("value")) {
encodeValue(code, vl.get("value"));
} else {
for (Iterator<Entry<String, Object>> it = vl.entrySet().iterator(); it.hasNext(); ) {
Entry<String, Object> e = it.next();
code.add(e.getKey());
code.add(" = ");
encodeValue(code, e.getValue());
if (it.hasNext()) {
code.add(", ");
}
}
}
code.add(')');
}
}
@SuppressWarnings("unchecked")
public void addThrows(MethodNode mth, CodeWriter code) {
Annotation an = mth.getAttributes().getAnnotation(Consts.DALVIK_THROWS);
if (an != null) {
Object exs = an.getDefaultValue();
code.add(" throws ");
for (Iterator<ArgType> it = ((List<ArgType>) exs).iterator(); it.hasNext(); ) {
ArgType ex = it.next();
code.add(TypeGen.translate(classGen, ex));
if (it.hasNext()) {
code.add(", ");
}
}
}
}
public Object getAnnotationDefaultValue(String name) {
Annotation an = cls.getAttributes().getAnnotation(Consts.DALVIK_ANNOTATION_DEFAULT);
if (an != null) {
Annotation defAnnotation = (Annotation) an.getDefaultValue();
return defAnnotation.getValues().get(name);
}
return null;
}
// TODO: refactor this boilerplate code
public void encodeValue(CodeWriter code, Object val) {
if (val == null) {
code.add("null");
return;
}
if (val instanceof String) {
code.add(StringUtils.unescapeString((String) val));
} else if (val instanceof Integer) {
code.add(TypeGen.formatInteger((Integer) val));
} else if (val instanceof Character) {
code.add(StringUtils.unescapeChar((Character) val));
} else if (val instanceof Boolean) {
code.add(Boolean.TRUE.equals(val) ? "true" : "false");
} else if (val instanceof Float) {
code.add(TypeGen.formatFloat((Float) val));
} else if (val instanceof Double) {
code.add(TypeGen.formatDouble((Double) val));
} else if (val instanceof Long) {
code.add(TypeGen.formatLong((Long) val));
} else if (val instanceof Short) {
code.add(TypeGen.formatShort((Short) val));
} else if (val instanceof Byte) {
code.add(TypeGen.formatByte((Byte) val));
} else if (val instanceof ArgType) {
code.add(TypeGen.translate(classGen, (ArgType) val)).add(".class");
} else if (val instanceof FieldInfo) {
// must be a static field
FieldInfo field = (FieldInfo) val;
code.add(InsnGen.makeStaticFieldAccess(field, classGen));
} else if (val instanceof List) {
code.add('{');
Iterator<?> it = ((List) val).iterator();
while (it.hasNext()) {
Object obj = it.next();
encodeValue(code, obj);
if (it.hasNext()) {
code.add(", ");
}
}
code.add('}');
} else if (val instanceof Annotation) {
formatAnnotation(code, (Annotation) val);
} else {
// TODO: also can be method values
throw new JadxRuntimeException("Can't decode value: " + val + " (" + val.getClass() + ")");
}
}
}
@@ -0,0 +1,461 @@
package jadx.core.codegen;
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.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;
private final boolean fallback;
private final Set<ClassInfo> imports = new HashSet<ClassInfo>();
private int clsDeclLine = 0;
public ClassGen(ClassNode cls, ClassGen parentClsGen, boolean fallback) {
this.cls = cls;
this.parentGen = parentClsGen;
this.fallback = fallback;
this.annotationGen = new AnnotationGen(cls, this);
}
public ClassNode getClassNode() {
return cls;
}
public CodeWriter makeClass() throws CodegenException {
CodeWriter clsBody = new CodeWriter();
addClassCode(clsBody);
CodeWriter clsCode = new CodeWriter();
if (!"".equals(cls.getPackage())) {
clsCode.add("package ").add(cls.getPackage()).add(';');
clsCode.newLine();
}
int importsCount = imports.size();
if (importsCount != 0) {
List<String> sortImports = new ArrayList<String>(importsCount);
for (ClassInfo ic : imports) {
sortImports.add(ic.getFullName());
}
Collections.sort(sortImports);
for (String imp : sortImports) {
clsCode.startLine("import ").add(imp).add(';');
}
clsCode.newLine();
sortImports.clear();
imports.clear();
}
clsCode.add(clsBody);
return clsCode;
}
public void addClassCode(CodeWriter code) throws CodegenException {
if (cls.getAttributes().contains(AttributeFlag.DONT_GENERATE)) {
return;
}
if (cls.getAttributes().contains(AttributeFlag.INCONSISTENT_CODE)) {
code.startLine("// jadx: inconsistent code");
}
addClassDeclaration(code);
addClassBody(code);
}
public void addClassDeclaration(CodeWriter clsCode) {
AccessInfo af = cls.getAccessFlags();
if (af.isInterface()) {
af = af.remove(AccessFlags.ACC_ABSTRACT);
} else if (af.isEnum()) {
af = af.remove(AccessFlags.ACC_FINAL).remove(AccessFlags.ACC_ABSTRACT);
}
annotationGen.addForClass(clsCode);
insertSourceFileInfo(clsCode, cls);
clsCode.startLine(af.makeString());
if (af.isInterface()) {
if (af.isAnnotation()) {
clsCode.add('@');
}
clsCode.add("interface ");
} else if (af.isEnum()) {
clsCode.add("enum ");
} else {
clsCode.add("class ");
}
clsCode.add(cls.getShortName());
addGenericMap(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)).add(' ');
}
if (cls.getInterfaces().size() > 0 && !af.isAnnotation()) {
if (cls.getAccessFlags().isInterface()) {
clsCode.add("extends ");
} else {
clsCode.add("implements ");
}
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.attachDefinition(cls);
}
public boolean addGenericMap(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 addClassBody(CodeWriter clsCode) throws CodegenException {
clsCode.add('{');
clsDeclLine = clsCode.getLine();
clsCode.incIndent();
addFields(clsCode);
addInnerClasses(clsCode, cls);
addMethods(clsCode);
clsCode.decIndent();
clsCode.startLine('}');
}
private void addInnerClasses(CodeWriter code, ClassNode cls) throws CodegenException {
for (ClassNode innerCls : cls.getInnerClasses()) {
if (!innerCls.isAnonymous()) {
ClassGen inClGen = new ClassGen(innerCls, getParentGen(), fallback);
code.newLine();
inClGen.addClassCode(code);
imports.addAll(inClGen.getImports());
}
}
}
private void addMethods(CodeWriter code) {
for (MethodNode mth : cls.getMethods()) {
if (!mth.getAttributes().contains(AttributeFlag.DONT_GENERATE)) {
try {
if (code.getLine() != clsDeclLine) {
code.newLine();
}
addMethod(code, mth);
} catch (Exception e) {
String msg = ErrorsCounter.methodError(mth, "Method generation error", e);
code.startLine("/* " + msg + CodeWriter.NL + Utils.getStackTrace(e) + " */");
}
}
}
}
private void addMethod(CodeWriter code, MethodNode mth) throws CodegenException {
MethodGen mthGen = new MethodGen(this, mth);
if (mth.getAccessFlags().isAbstract() || mth.getAccessFlags().isNative()) {
mthGen.addDefinition(code);
if (cls.getAccessFlags().isAnnotation()) {
Object def = annotationGen.getAnnotationDefaultValue(mth.getName());
if (def != null) {
code.add(" default ");
annotationGen.encodeValue(code, def);
}
}
code.add(';');
} else {
boolean badCode = mth.getAttributes().contains(AttributeFlag.INCONSISTENT_CODE);
if (badCode) {
code.startLine("/* JADX WARNING: inconsistent code. */");
code.startLine("/* Code decompiled incorrectly, please refer to instructions dump. */");
LOG.error(ErrorsCounter.formatErrorMsg(mth, " Inconsistent code"));
}
if (mthGen.addDefinition(code)) {
code.add(' ');
}
code.add('{');
code.incIndent();
insertSourceFileInfo(code, mth);
mthGen.addInstructions(code);
code.decIndent();
code.startLine('}');
}
}
private void addFields(CodeWriter code) throws CodegenException {
addEnumFields(code);
for (FieldNode f : cls.getFields()) {
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(f.getName());
FieldValueAttr fv = (FieldValueAttr) f.getAttributes().get(AttributeType.FIELD_VALUE);
if (fv != null) {
code.add(" = ");
if (fv.getValue() == null) {
code.add(TypeGen.literalToString(0, f.getType()));
} else {
annotationGen.encodeValue(code, fv.getValue());
}
}
code.add(';');
code.attachDefinition(f);
}
}
private void addEnumFields(CodeWriter code) throws CodegenException {
EnumClassAttr enumFields = (EnumClassAttr) cls.getAttributes().get(AttributeType.ENUM_CLASS);
if (enumFields != null) {
InsnGen igen = null;
for (Iterator<EnumField> it = enumFields.getFields().iterator(); it.hasNext(); ) {
EnumField f = it.next();
code.startLine(f.getName());
if (f.getArgs().size() != 0) {
code.add('(');
for (Iterator<InsnArg> aIt = f.getArgs().iterator(); aIt.hasNext(); ) {
InsnArg arg = aIt.next();
if (igen == null) {
// don't init mth gen if this is simple enum
MethodGen mthGen = new MethodGen(this, enumFields.getStaticMethod());
igen = new InsnGen(mthGen, false);
}
igen.addArg(code, arg);
if (aIt.hasNext()) {
code.add(", ");
}
}
code.add(')');
}
if (f.getCls() != null) {
new ClassGen(f.getCls(), this, fallback).addClassBody(code);
}
if (it.hasNext()) {
code.add(',');
}
}
if (enumFields.getFields().isEmpty()) {
code.startLine();
}
code.add(';');
code.newLine();
}
}
public String useClass(ArgType clsType) {
if (clsType.isGenericType()) {
return clsType.getObject();
}
return useClass(ClassInfo.fromType(clsType));
}
public String useClass(ClassInfo classInfo) {
String baseClass = useClassInternal(cls.getClassInfo(), classInfo);
ArgType type = classInfo.getType();
ArgType[] generics = type.getGenericTypes();
if (generics == null) {
return baseClass;
}
StringBuilder sb = new StringBuilder();
sb.append(baseClass);
sb.append('<');
int len = generics.length;
for (int i = 0; i < len; i++) {
if (i != 0) {
sb.append(", ");
}
ArgType gt = generics[i];
ArgType wt = gt.getWildcardType();
if (wt != null) {
sb.append('?');
int bounds = gt.getWildcardBounds();
if (bounds != 0) {
sb.append(bounds == -1 ? " super " : " extends ");
sb.append(TypeGen.translate(this, wt));
}
} else {
sb.append(TypeGen.translate(this, gt));
}
}
sb.append('>');
return sb.toString();
}
private String useClassInternal(ClassInfo useCls, ClassInfo classInfo) {
String fullName = classInfo.getFullName();
if (fallback) {
return fullName;
}
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 (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;
}
// don't add import if class not public (must be accessed using inheritance)
ClassNode classNode = cls.dex().resolveClass(classInfo);
if (classNode != null && !classNode.getAccessFlags().isPublic()) {
return shortName;
}
if (searchCollision(cls.dex(), useCls, shortName)) {
return fullName;
}
if (classInfo.getPackage().equals(useCls.getPackage())) {
fullName = classInfo.getNameWithoutPackage();
}
for (ClassInfo importCls : getImports()) {
if (!importCls.equals(classInfo)
&& importCls.getShortName().equals(shortName)) {
if (classInfo.isInner()) {
String parent = useClassInternal(useCls, classInfo.getParentClass());
return parent + "." + shortName;
} else {
return fullName;
}
}
}
addImport(classInfo);
return shortName;
}
}
private void addImport(ClassInfo classInfo) {
if (parentGen != null) {
parentGen.addImport(classInfo);
} else {
imports.add(classInfo);
}
}
private Set<ClassInfo> getImports() {
if (parentGen != null) {
return parentGen.getImports();
} else {
return imports;
}
}
private static boolean isClassInnerFor(ClassInfo inner, ClassInfo parent) {
if (inner.isInner()) {
ClassInfo p = inner.getParentClass();
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) {
SourceFileAttr sourceFileAttr = (SourceFileAttr) node.getAttributes().get(AttributeType.SOURCE_FILE);
if (sourceFileAttr != null) {
code.startLine("// compiled from: ").add(sourceFileAttr.getFileName());
}
}
public ClassGen getParentGen() {
return parentGen == null ? this : parentGen;
}
public AnnotationGen getAnnotationGen() {
return annotationGen;
}
public boolean isFallbackMode() {
return fallback;
}
}
@@ -1,30 +1,24 @@
package jadx.codegen;
package jadx.core.codegen;
import jadx.IJadxArgs;
import jadx.dex.nodes.ClassNode;
import jadx.dex.visitors.AbstractVisitor;
import jadx.utils.exceptions.CodegenException;
import java.io.File;
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 File dir;
private final IJadxArgs args;
public CodeGen(IJadxArgs args) {
this.args = args;
this.dir = args.getOutDir();
}
@Override
public boolean visit(ClassNode cls) throws CodegenException {
ClassGen clsGen = new ClassGen(cls, null, isFallbackMode());
CodeWriter clsCode = clsGen.makeClass();
String fileName = cls.getClassInfo().getFullPath() + ".java";
if (isFallbackMode())
fileName += ".jadx";
clsCode.save(dir, fileName);
clsCode.finish();
cls.setCode(clsCode);
return false;
}
@@ -0,0 +1,284 @@
package jadx.core.codegen;
import jadx.api.CodePosition;
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.Iterator;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class CodeWriter {
private static final Logger LOG = LoggerFactory.getLogger(CodeWriter.class);
private static final int MAX_FILENAME_LENGTH = 128;
public static final String NL = System.getProperty("line.separator");
public static final String INDENT = " ";
private static final String[] INDENT_CACHE = {
"",
INDENT,
INDENT + INDENT,
INDENT + INDENT + INDENT,
INDENT + INDENT + INDENT + INDENT,
INDENT + INDENT + INDENT + INDENT + INDENT,
};
private final StringBuilder buf = new StringBuilder();
private String indentStr;
private int indent;
private int line = 1;
private int offset = 0;
private Map<CodePosition, Object> annotations = Collections.emptyMap();
public CodeWriter() {
this.indent = 0;
this.indentStr = "";
}
public CodeWriter(int indent) {
this.indent = indent;
updateIndent();
}
public CodeWriter startLine() {
addLine();
addLineIndent();
return this;
}
public CodeWriter startLine(char c) {
addLine();
addLineIndent();
add(c);
return this;
}
public CodeWriter startLine(String str) {
addLine();
addLineIndent();
add(str);
return this;
}
public CodeWriter add(Object obj) {
add(obj.toString());
return this;
}
public CodeWriter add(String str) {
buf.append(str);
offset += str.length();
return this;
}
public CodeWriter add(char c) {
buf.append(c);
offset++;
return this;
}
CodeWriter add(CodeWriter code) {
line--;
for (Map.Entry<CodePosition, Object> entry : code.annotations.entrySet()) {
CodePosition pos = entry.getKey();
attachAnnotation(entry.getValue(), new CodePosition(line + pos.getLine(), pos.getOffset()));
}
line += code.line;
offset = code.offset;
buf.append(code);
return this;
}
public CodeWriter newLine() {
addLine();
return this;
}
private void addLine() {
buf.append(NL);
line++;
offset = 0;
}
private CodeWriter addLineIndent() {
buf.append(indentStr);
offset += indentStr.length();
return this;
}
public CodeWriter addIndent() {
add(INDENT);
return this;
}
private void updateIndent() {
int curIndent = indent;
if (curIndent < INDENT_CACHE.length) {
this.indentStr = INDENT_CACHE[curIndent];
} else {
StringBuilder s = new StringBuilder(curIndent * INDENT.length());
for (int i = 0; i < curIndent; i++) {
s.append(INDENT);
}
this.indentStr = s.toString();
}
}
public void incIndent() {
incIndent(1);
}
public void decIndent() {
decIndent(1);
}
public void incIndent(int c) {
this.indent += c;
updateIndent();
}
public void decIndent(int c) {
this.indent -= c;
if (this.indent < 0) {
LOG.warn("Indent < 0");
this.indent = 0;
}
updateIndent();
}
public int getLine() {
return line;
}
private static class DefinitionWrapper {
private final LineAttrNode node;
private DefinitionWrapper(LineAttrNode node) {
this.node = node;
}
public LineAttrNode getNode() {
return node;
}
}
public Object attachDefinition(LineAttrNode obj) {
return attachAnnotation(new DefinitionWrapper(obj), new CodePosition(line, offset));
}
public Object attachAnnotation(Object obj) {
return attachAnnotation(obj, new CodePosition(line, offset + 1));
}
private Object attachAnnotation(Object obj, CodePosition pos) {
if (annotations.isEmpty()) {
annotations = new HashMap<CodePosition, Object>();
}
return annotations.put(pos, obj);
}
public Map<CodePosition, Object> getAnnotations() {
return annotations;
}
public void finish() {
buf.trimToSize();
Iterator<Map.Entry<CodePosition, Object>> it = annotations.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<CodePosition, Object> entry = it.next();
Object v = entry.getValue();
if (v instanceof DefinitionWrapper) {
LineAttrNode l = ((DefinitionWrapper) v).getNode();
l.setDecompiledLine(entry.getKey().getLine());
it.remove();
}
}
}
private static String removeFirstEmptyLine(String str) {
if (str.startsWith(NL)) {
return str.substring(NL.length());
} else {
return str;
}
}
public int length() {
return buf.length();
}
public boolean isEmpty() {
return buf.length() == 0;
}
public boolean notEmpty() {
return buf.length() != 0;
}
@Override
public String toString() {
return buf.toString();
}
public void save(File dir, String subDir, String fileName) {
save(dir, new File(subDir, fileName).getPath());
}
public void save(File dir, String fileName) {
save(new File(dir, fileName));
}
public void save(File file) {
String name = file.getName();
if (name.length() > MAX_FILENAME_LENGTH) {
int dotIndex = name.indexOf('.');
int cutAt = MAX_FILENAME_LENGTH - name.length() + dotIndex - 1;
if (cutAt <= 0) {
name = name.substring(0, MAX_FILENAME_LENGTH - 1);
} else {
name = name.substring(0, cutAt) + name.substring(dotIndex);
}
file = new File(file.getParentFile(), name);
}
PrintWriter out = null;
try {
Utils.makeDirsForFile(file);
out = new PrintWriter(file, "UTF-8");
String code = buf.toString();
code = removeFirstEmptyLine(code);
out.print(code);
} catch (Exception e) {
LOG.error("Save file error", e);
} finally {
if (out != null) {
out.close();
}
}
}
@Override
public int hashCode() {
return buf.toString().hashCode();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof CodeWriter)) {
return false;
}
CodeWriter that = (CodeWriter) o;
return buf.toString().equals(that.buf.toString());
}
}
@@ -0,0 +1,135 @@
package jadx.core.codegen;
import jadx.core.dex.instructions.ArithNode;
import jadx.core.dex.instructions.IfOp;
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.LiteralArg;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.regions.Compare;
import jadx.core.dex.regions.IfCondition;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.exceptions.CodegenException;
import jadx.core.utils.exceptions.JadxRuntimeException;
import java.util.Iterator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ConditionGen extends InsnGen {
private static final Logger LOG = LoggerFactory.getLogger(ConditionGen.class);
public ConditionGen(InsnGen insnGen) {
super(insnGen.mgen, insnGen.fallback);
}
void add(CodeWriter code, IfCondition condition) throws CodegenException {
switch (condition.getMode()) {
case COMPARE:
addCompare(code, condition.getCompare());
break;
case NOT:
addNot(code, condition);
break;
case AND:
case OR:
addAndOr(code, condition);
break;
default:
throw new JadxRuntimeException("Unknown condition mode: " + condition);
}
}
void wrap(CodeWriter code, IfCondition cond) throws CodegenException {
boolean wrap = isWrapNeeded(cond);
if (wrap) {
code.add('(');
}
add(code, cond);
if (wrap) {
code.add(')');
}
}
private void addCompare(CodeWriter code, 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) {
// == true
addArg(code, firstArg, false);
return;
} else if (op == IfOp.NE) {
// != true
code.add('!');
boolean wrap = isWrapNeeded(firstArg);
if (wrap) {
code.add('(');
}
addArg(code, firstArg, false);
if (wrap) {
code.add(')');
}
return;
}
LOG.warn(ErrorsCounter.formatErrorMsg(mth, "Unsupported boolean condition " + op.getSymbol()));
}
addArg(code, firstArg, isWrapNeeded(firstArg));
code.add(' ').add(op.getSymbol()).add(' ');
addArg(code, secondArg, isWrapNeeded(secondArg));
}
private void addNot(CodeWriter code, IfCondition condition) throws CodegenException {
code.add('!');
wrap(code, condition.getArgs().get(0));
}
private void addAndOr(CodeWriter code, IfCondition condition) throws CodegenException {
String mode = condition.getMode() == IfCondition.Mode.AND ? " && " : " || ";
Iterator<IfCondition> it = condition.getArgs().iterator();
while (it.hasNext()) {
wrap(code, it.next());
if (it.hasNext()) {
code.add(mode);
}
}
}
private boolean isWrapNeeded(IfCondition condition) {
return !condition.isCompare();
}
private static boolean isWrapNeeded(InsnArg arg) {
if (!arg.isInsnWrap()) {
return false;
}
InsnNode insn = ((InsnWrapArg) arg).getWrapInsn();
if (insn.getType() == InsnType.ARITH) {
switch (((ArithNode) insn).getOp()) {
case ADD:
case SUB:
case MUL:
case DIV:
case REM:
return false;
}
} else if (insn.getType() == InsnType.INVOKE) {
return false;
}
return true;
}
}
@@ -0,0 +1,760 @@
package jadx.core.codegen;
import jadx.core.dex.attributes.AttributeFlag;
import jadx.core.dex.attributes.AttributeType;
import jadx.core.dex.attributes.FieldReplaceAttr;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.attributes.MethodInlineAttr;
import jadx.core.dex.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.InsnType;
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.instructions.mods.TernaryInsn;
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 jadx.core.utils.exceptions.JadxRuntimeException;
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 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;
protected final boolean fallback;
private static enum Flags {
BODY_ONLY,
BODY_ONLY_NOWRAP,
}
public InsnGen(MethodGen mgen, boolean fallback) {
this.mgen = mgen;
this.mth = mgen.getMethodNode();
this.root = mth.dex().root();
this.fallback = fallback;
}
private boolean isFallback() {
return fallback;
}
public void addArgDot(CodeWriter code, InsnArg arg) throws CodegenException {
int len = code.length();
addArg(code, arg, true);
if (len != code.length()) {
code.add('.');
}
}
public void addArg(CodeWriter code, InsnArg arg) throws CodegenException {
addArg(code, arg, true);
}
public void addArg(CodeWriter code, InsnArg arg, boolean wrap) throws CodegenException {
if (arg.isRegister()) {
code.add(mgen.makeArgName((RegisterArg) arg));
} else if (arg.isLiteral()) {
code.add(lit((LiteralArg) arg));
} else if (arg.isInsnWrap()) {
Flags flag = wrap ? Flags.BODY_ONLY : Flags.BODY_ONLY_NOWRAP;
makeInsn(((InsnWrapArg) arg).getWrapInsn(), code, flag);
} else if (arg.isNamed()) {
code.add(((NamedArg) arg).getName());
} else if (arg.isField()) {
FieldArg f = (FieldArg) arg;
if (f.isStatic()) {
code.add(staticField(f.getField()));
} else {
RegisterArg regArg = new RegisterArg(f.getRegNum());
regArg.replaceTypedVar(f);
instanceField(code, f.getField(), regArg);
}
} else {
throw new CodegenException("Unknown arg type " + arg);
}
}
public void assignVar(CodeWriter code, InsnNode insn) throws CodegenException {
RegisterArg arg = insn.getResult();
if (insn.getAttributes().contains(AttributeFlag.DECLARE_VAR)) {
declareVar(code, arg);
} else {
addArg(code, arg, false);
}
}
public void declareVar(CodeWriter code, RegisterArg arg) {
code.add(useType(arg.getType()));
code.add(' ');
code.add(mgen.assignArg(arg));
}
private static String lit(LiteralArg arg) {
return TypeGen.literalToString(arg.getLiteral(), arg.getType());
}
private void instanceField(CodeWriter code, FieldInfo field, InsnArg arg) throws CodegenException {
FieldNode fieldNode = mth.getParentClass().searchField(field);
if (fieldNode != null) {
FieldReplaceAttr replace = (FieldReplaceAttr) fieldNode.getAttributes().get(AttributeType.FIELD_REPLACE);
if (replace != null) {
FieldInfo info = replace.getFieldInfo();
if (replace.isOuterClass()) {
code.add(useClass(info.getDeclClass())).add(".this");
}
return;
}
}
addArgDot(code, arg);
code.add(field.getName());
}
public static String makeStaticFieldAccess(FieldInfo field, ClassGen clsGen) {
ClassInfo declClass = field.getDeclClass();
if (clsGen.getClassNode().getFullName().startsWith(declClass.getFullName())) {
return field.getName();
}
// Android specific resources class handler
ClassInfo parentClass = declClass.getParentClass();
if (parentClass != null && parentClass.getShortName().equals("R")) {
return clsGen.useClass(parentClass) + "." + declClass.getShortName() + "." + field.getName();
}
return clsGen.useClass(declClass) + '.' + field.getName();
}
protected String staticField(FieldInfo field) {
return makeStaticFieldAccess(field, mgen.getClassGen());
}
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, Flags flag) throws CodegenException {
try {
if (insn.getType() == InsnType.NOP) {
return false;
}
EnumSet<Flags> state = EnumSet.noneOf(Flags.class);
if (flag == Flags.BODY_ONLY || flag == Flags.BODY_ONLY_NOWRAP) {
state.add(flag);
makeInsnBody(code, insn, state);
} else {
code.startLine();
if (insn.getSourceLine() != 0) {
code.attachAnnotation(insn.getSourceLine());
}
if (insn.getResult() != null && insn.getType() != InsnType.ARITH_ONEARG) {
assignVar(code, insn);
code.add(" = ");
}
makeInsnBody(code, insn, state);
code.add(';');
}
} catch (Throwable th) {
throw new CodegenException(mth, "Error generate insn: " + insn, th);
}
return true;
}
private void makeInsnBody(CodeWriter code, InsnNode insn, EnumSet<Flags> 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:
addArg(code, insn.getArg(0), false);
break;
case CHECK_CAST:
case CAST: {
boolean wrap = state.contains(Flags.BODY_ONLY);
if (wrap) {
code.add('(');
}
code.add('(');
code.add(useType((ArgType) ((IndexInsnNode) insn).getIndex()));
code.add(") ");
addArg(code, insn.getArg(0), true);
if (wrap) {
code.add(')');
}
break;
}
case ARITH:
makeArith((ArithNode) insn, code, state);
break;
case ARITH_ONEARG:
makeArithOneArg((ArithNode) insn, code, state);
break;
case NEG: {
boolean wrap = state.contains(Flags.BODY_ONLY);
if (wrap) {
code.add('(');
}
code.add('-');
addArg(code, insn.getArg(0));
if (wrap) {
code.add(')');
}
break;
}
case RETURN:
if (insn.getArgsCount() != 0) {
code.add("return ");
addArg(code, 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 ");
addArg(code, insn.getArg(0), true);
break;
case CMP_L:
case CMP_G:
code.add('(');
addArg(code, insn.getArg(0));
code.add(" > ");
addArg(code, insn.getArg(1));
code.add(" ? 1 : (");
addArg(code, insn.getArg(0));
code.add(" == ");
addArg(code, insn.getArg(1));
code.add("? 0 : -1))");
break;
case INSTANCE_OF: {
boolean wrap = state.contains(Flags.BODY_ONLY);
if (wrap) {
code.add('(');
}
addArg(code, insn.getArg(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();
code.add("new ").add(useType(arrayType.getArrayRootElement()));
code.add('[');
addArg(code, insn.getArg(0));
code.add(']');
int dim = arrayType.getArrayDimension();
for (int i = 0; i < dim - 1; i++) {
code.add("[]");
}
break;
}
case ARRAY_LENGTH:
addArg(code, insn.getArg(0));
code.add(".length");
break;
case FILL_ARRAY:
fillArray((FillArrayNode) insn, code);
break;
case FILLED_NEW_ARRAY:
filledNewArray(insn, code);
break;
case AGET:
addArg(code, insn.getArg(0));
code.add('[');
addArg(code, insn.getArg(1), false);
code.add(']');
break;
case APUT:
addArg(code, insn.getArg(0));
code.add('[');
addArg(code, insn.getArg(1), false);
code.add("] = ");
addArg(code, insn.getArg(2), false);
break;
case IGET: {
FieldInfo fieldInfo = (FieldInfo) ((IndexInsnNode) insn).getIndex();
instanceField(code, fieldInfo, insn.getArg(0));
break;
}
case IPUT: {
FieldInfo fieldInfo = (FieldInfo) ((IndexInsnNode) insn).getIndex();
instanceField(code, fieldInfo, insn.getArg(1));
code.add(" = ");
addArg(code, insn.getArg(0), false);
break;
}
case SGET:
code.add(staticField((FieldInfo) ((IndexInsnNode) insn).getIndex()));
break;
case SPUT:
FieldInfo field = (FieldInfo) ((IndexInsnNode) insn).getIndex();
code.add(staticField(field)).add(" = ");
addArg(code, insn.getArg(0), false);
break;
case STR_CONCAT:
boolean wrap = state.contains(Flags.BODY_ONLY);
if (wrap) {
code.add('(');
}
for (Iterator<InsnArg> it = insn.getArguments().iterator(); it.hasNext(); ) {
addArg(code, it.next());
if (it.hasNext()) {
code.add(" + ");
}
}
if (wrap) {
code.add(')');
}
break;
case MONITOR_ENTER:
if (isFallback()) {
code.add("monitor-enter(");
addArg(code, insn.getArg(0));
code.add(')');
}
break;
case MONITOR_EXIT:
if (isFallback()) {
code.add("monitor-exit(");
addArg(code, insn.getArg(0));
code.add(')');
}
break;
case MOVE_EXCEPTION:
if (isFallback()) {
code.add("move-exception");
} else {
addArg(code, insn.getArg(0));
}
break;
case TERNARY:
makeTernary((TernaryInsn) insn, code, state);
break;
case ARGS:
addArg(code, insn.getArg(0));
break;
/* fallback mode instructions */
case IF:
assert isFallback() : "if insn in not fallback mode";
IfNode ifInsn = (IfNode) insn;
code.add("if (");
addArg(code, insn.getArg(0));
code.add(' ');
code.add(ifInsn.getOp().getSymbol()).add(' ');
addArg(code, insn.getArg(1));
code.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(");
addArg(code, insn.getArg(0));
code.add(") {");
code.incIndent();
for (int i = 0; i < sw.getCasesCount(); i++) {
code.startLine("case ").add(sw.getKeys()[i]).add(": goto ");
code.add(MethodGen.getLabelName(sw.getTargets()[i])).add(';');
}
code.startLine("default: goto ");
code.add(MethodGen.getLabelName(sw.getDefaultCaseOffset())).add(';');
code.decIndent();
code.startLine('}');
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++) {
addArg(code, insn.getArg(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<Flags> 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();
}
cls.getAttributes().add(AttributeFlag.DONT_GENERATE);
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("() ");
new ClassGen(cls, mgen.getClassGen().getParentGen(), fallback).addClassBody(code);
return;
}
if (insn.isSelf()) {
throw new JadxRuntimeException("Constructor 'self' invoke must be removed!");
}
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()) {
addArgDot(code, arg);
}
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;
}
if (callMthNode != null) {
code.attachAnnotation(callMthNode);
}
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(')');
addArg(code, arg, true);
} else {
addArg(code, arg, false);
}
if (i < argsCount - 1) {
code.add(", ");
}
origPos++;
}
code.add(')');
} else {
code.add('(');
if (k < argsCount) {
addArg(code, insn.getArg(k), false);
for (int i = k + 1; i < argsCount; i++) {
code.add(", ");
addArg(code, insn.getArg(i), false);
}
}
code.add(')');
}
}
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, Flags.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, Flags.BODY_ONLY);
// revert changes
for (Map.Entry<RegisterArg, InsnArg> e : toRevert.entrySet()) {
inl.replaceArg(e.getValue(), e.getKey());
}
}
}
private void makeTernary(TernaryInsn insn, CodeWriter code, EnumSet<Flags> state) throws CodegenException {
boolean wrap = state.contains(Flags.BODY_ONLY);
if (wrap) {
code.add('(');
}
InsnArg first = insn.getArg(0);
InsnArg second = insn.getArg(1);
ConditionGen condGen = new ConditionGen(this);
if (first.equals(LiteralArg.TRUE) && second.equals(LiteralArg.FALSE)) {
condGen.add(code, insn.getCondition());
} else {
condGen.wrap(code, insn.getCondition());
code.add(" ? ");
addArg(code, first, false);
code.add(" : ");
addArg(code, second, false);
}
if (wrap) {
code.add(')');
}
}
private void makeArith(ArithNode insn, CodeWriter code, EnumSet<Flags> state) throws CodegenException {
// wrap insn in brackets for save correct operation order
boolean wrap = state.contains(Flags.BODY_ONLY)
&& !insn.getAttributes().contains(AttributeFlag.DONT_WRAP);
if (wrap) {
code.add('(');
}
addArg(code, insn.getArg(0));
code.add(' ');
code.add(insn.getOp().getSymbol());
code.add(' ');
addArg(code, insn.getArg(1));
if (wrap) {
code.add(')');
}
}
private void makeArithOneArg(ArithNode insn, CodeWriter code, EnumSet<Flags> state) throws CodegenException {
ArithOp op = insn.getOp();
InsnArg arg = insn.getArg(0);
// "++" or "--"
if (arg.isLiteral() && (op == ArithOp.ADD || op == ArithOp.SUB)) {
LiteralArg lit = (LiteralArg) arg;
if (lit.isInteger() && lit.getLiteral() == 1) {
assignVar(code, insn);
String opSymbol = op.getSymbol();
code.add(opSymbol).add(opSymbol);
return;
}
}
// +=, -= ...
assignVar(code, insn);
code.add(' ').add(op.getSymbol()).add("= ");
addArg(code, arg, false);
}
}
@@ -0,0 +1,332 @@
package jadx.core.codegen;
import jadx.core.Consts;
import jadx.core.dex.attributes.AttributeFlag;
import jadx.core.dex.attributes.AttributeType;
import jadx.core.dex.attributes.AttributesList;
import jadx.core.dex.attributes.JadxErrorAttr;
import jadx.core.dex.attributes.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.regions.Region;
import jadx.core.dex.trycatch.CatchAttr;
import jadx.core.dex.visitors.DepthTraversal;
import jadx.core.dex.visitors.FallbackModeVisitor;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.InsnUtils;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.CodegenException;
import jadx.core.utils.exceptions.DecodeException;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.android.dx.rop.code.AccessFlags;
public class MethodGen {
private static final Logger LOG = LoggerFactory.getLogger(MethodGen.class);
private final MethodNode mth;
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;
this.fallback = classGen.isFallbackMode();
this.annotationGen = classGen.getAnnotationGen();
List<RegisterArg> args = mth.getArguments(true);
for (RegisterArg arg : args) {
varNames.add(makeArgName(arg));
}
}
public ClassGen getClassGen() {
return classGen;
}
public MethodNode getMethodNode() {
return mth;
}
public boolean addDefinition(CodeWriter code) {
if (mth.getMethodInfo().isClassInit()) {
code.startLine("static");
code.attachDefinition(mth);
return true;
}
if (mth.getAttributes().contains(AttributeFlag.ANONYMOUS_CONSTRUCTOR)) {
// don't add method name and arguments
code.startLine();
code.attachDefinition(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.addGenericMap(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)"
));
}
}
addMethodArguments(code, args);
code.add(')');
annotationGen.addThrows(mth, code);
code.attachDefinition(mth);
return true;
}
private void addMethodArguments(CodeWriter argsCode, List<RegisterArg> args) {
MethodParameters paramsAnnotation =
(MethodParameters) mth.getAttributes().get(AttributeType.ANNOTATION_MTH_PARAMETERS);
int i = 0;
for (Iterator<RegisterArg> it = args.iterator(); it.hasNext(); ) {
RegisterArg arg = it.next();
// add argument annotation
if (paramsAnnotation != null) {
annotationGen.addForParameter(argsCode, paramsAnnotation, i);
}
if (!it.hasNext() && mth.getAccessFlags().isVarArgs()) {
// change last array argument to varargs
ArgType type = arg.getType();
if (type.isArray()) {
ArgType elType = type.getArrayElement();
argsCode.add(TypeGen.translate(classGen, elType));
argsCode.add(" ...");
} else {
LOG.warn(ErrorsCounter.formatErrorMsg(mth, "Last argument in varargs method not array"));
argsCode.add(TypeGen.translate(classGen, arg.getType()));
}
} else {
argsCode.add(TypeGen.translate(classGen, arg.getType()));
}
argsCode.add(' ');
argsCode.add(makeArgName(arg));
i++;
if (it.hasNext()) {
argsCode.add(", ");
}
}
}
/**
* Make variable name for register,
* Name contains register number and
* variable type or name (if debug info available)
*/
public String makeArgName(RegisterArg arg) {
String name = arg.getTypedVar().getName();
String base = "r" + arg.getRegNum();
if (fallback) {
if (name != null) {
return base + "_" + name;
}
return base;
} else {
if (name != null) {
if (Consts.DEBUG) {
return base + "_" + name;
}
return name;
} else {
ArgType type = arg.getType();
if (type.isPrimitive()) {
return base + type.getPrimitiveType().getShortName().toLowerCase();
} else {
return base + "_" + Utils.escape(TypeGen.translate(classGen, arg.getType()));
}
}
}
}
/**
* Put variable declaration and return variable name (used for assignments)
*
* @param arg register variable
* @return variable name
*/
public String assignArg(RegisterArg arg) {
String name = makeArgName(arg);
if (varNames.add(name) || fallback) {
return name;
}
name = getUniqVarName(name);
arg.getTypedVar().setName(name);
return name;
}
public String assignNamedArg(NamedArg arg) {
String name = arg.getName();
if (varNames.add(name) || fallback) {
return name;
}
name = getUniqVarName(name);
arg.setName(name);
return name;
}
private String getUniqVarName(String name) {
String r;
int i = 2;
do {
r = name + "_" + i;
i++;
} while (varNames.contains(r));
varNames.add(r);
return r;
}
public void addInstructions(CodeWriter code) throws CodegenException {
if (mth.getAttributes().contains(AttributeType.JADX_ERROR)) {
code.startLine("throw new UnsupportedOperationException(\"Method not decompiled: ");
code.add(mth.toString());
code.add("\");");
JadxErrorAttr err = (JadxErrorAttr) mth.getAttributes().get(AttributeType.JADX_ERROR);
code.startLine("/* JADX: method processing error */");
Throwable cause = err.getCause();
if (cause != null) {
code.newLine();
code.add("/*");
code.startLine("Error: ").add(Utils.getStackTrace(cause));
code.add("*/");
}
makeMethodDump(code);
} else if (mth.getAttributes().contains(AttributeFlag.INCONSISTENT_CODE)) {
code.startLine("/*");
addFallbackMethodCode(code);
code.startLine("*/");
code.newLine();
} else {
Region startRegion = mth.getRegion();
if (startRegion != null) {
(new RegionGen(this)).makeRegion(code, startRegion);
} else {
addFallbackMethodCode(code);
}
}
}
private void makeMethodDump(CodeWriter code) {
code.startLine("/*");
getFallbackMethodGen(mth).addDefinition(code);
code.add(" {");
code.incIndent();
addFallbackMethodCode(code);
code.decIndent();
code.startLine('}');
code.startLine("*/");
}
public void addFallbackMethodCode(CodeWriter code) {
if (mth.getInstructions() == null) {
// load original instructions
try {
mth.load();
DepthTraversal.visit(new FallbackModeVisitor(), mth);
} catch (DecodeException e) {
LOG.error("Error reload instructions in fallback mode:", e);
code.startLine("// Can't loadFile method instructions: " + e.getMessage());
return;
}
}
List<InsnNode> insns = mth.getInstructions();
if (insns == null) {
code.startLine("// Can't load method instructions.");
return;
}
if (mth.getThisArg() != null) {
code.startLine(getFallbackMethodGen(mth).makeArgName(mth.getThisArg())).add(" = this;");
}
addFallbackInsns(code, mth, insns, true);
}
public static void addFallbackInsns(CodeWriter code, MethodNode mth, List<InsnNode> insns, boolean addLabels) {
InsnGen insnGen = new InsnGen(getFallbackMethodGen(mth), true);
for (InsnNode insn : insns) {
AttributesList attrs = insn.getAttributes();
if (addLabels) {
if (attrs.contains(AttributeType.JUMP)
|| attrs.contains(AttributeType.EXC_HANDLER)) {
code.decIndent();
code.startLine(getLabelName(insn.getOffset()) + ":");
code.incIndent();
}
}
try {
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);
}
}
}
/**
* Return fallback variant of method codegen
*/
private static MethodGen getFallbackMethodGen(MethodNode mth) {
ClassGen clsGen = new ClassGen(mth.getParentClass(), null, true);
return new MethodGen(clsGen, mth);
}
public static String getLabelName(int offset) {
return "L_" + InsnUtils.formatOffset(offset);
}
}
@@ -0,0 +1,279 @@
package jadx.core.codegen;
import jadx.core.dex.attributes.AttributeFlag;
import jadx.core.dex.attributes.AttributeType;
import jadx.core.dex.attributes.DeclareVariablesAttr;
import jadx.core.dex.attributes.ForceReturnAttr;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.instructions.IndexInsnNode;
import jadx.core.dex.instructions.SwitchNode;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.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.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.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) {
super(mgen, false);
}
public void makeRegion(CodeWriter code, IContainer cont) throws CodegenException {
if (cont instanceof IBlock) {
makeSimpleBlock((IBlock) cont, code);
} else if (cont instanceof IRegion) {
declareVars(code, cont);
if (cont instanceof Region) {
makeSimpleRegion(code, (Region) cont);
} else if (cont instanceof IfRegion) {
makeIf((IfRegion) cont, code, true);
} else if (cont instanceof SwitchRegion) {
makeSwitch((SwitchRegion) cont, code);
} else if (cont instanceof LoopRegion) {
makeLoop((LoopRegion) cont, code);
} else if (cont instanceof SynchronizedRegion) {
makeSynchronizedRegion((SynchronizedRegion) cont, code);
}
} else {
throw new CodegenException("Not processed container: " + cont);
}
}
private void declareVars(CodeWriter code, IContainer cont) {
DeclareVariablesAttr declVars =
(DeclareVariablesAttr) cont.getAttributes().get(AttributeType.DECLARE_VARIABLES);
if (declVars != null) {
for (RegisterArg v : declVars.getVars()) {
code.startLine();
declareVar(code, v);
code.add(';');
}
}
}
private void makeSimpleRegion(CodeWriter code, Region region) throws CodegenException {
CatchAttr tc = (CatchAttr) region.getAttributes().get(AttributeType.CATCH_BLOCK);
if (tc != null) {
makeTryCatch(region, tc.getTryBlock(), code);
} else {
for (IContainer c : region.getSubBlocks()) {
makeRegion(code, c);
}
}
}
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);
}
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, boolean newLine) throws CodegenException {
if (region.getTernRegion() != null) {
makeSimpleBlock(region.getTernRegion().getBlock(), code);
return;
}
if (newLine) {
code.startLine();
}
code.add("if (");
new ConditionGen(this).add(code, region.getCondition());
code.add(") {");
makeRegionIndent(code, region.getThenRegion());
code.startLine('}');
IContainer els = region.getElseRegion();
if (els != null && RegionUtils.notEmpty(els)) {
code.add(" else ");
if (connectElseIf(code, els)) {
return;
}
code.add('{');
makeRegionIndent(code, els);
code.startLine('}');
}
}
/**
* Connect if-else-if block
*/
private boolean connectElseIf(CodeWriter code, IContainer els) throws CodegenException {
if (els instanceof Region) {
Region re = (Region) els;
List<IContainer> subBlocks = re.getSubBlocks();
if (subBlocks.size() == 1 && subBlocks.get(0) instanceof IfRegion) {
IfRegion ifRegion = (IfRegion) subBlocks.get(0);
if (ifRegion.getAttributes().contains(AttributeFlag.ELSE_IF_CHAIN)) {
makeIf(ifRegion, code, false);
return true;
}
}
}
return false;
}
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);
int last = headerInsns.size() - 1;
for (int i = 0; i < last; 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;
}
ConditionGen conditionGen = new ConditionGen(this);
if (region.isConditionAtEnd()) {
code.startLine("do {");
makeRegionIndent(code, region.getBody());
code.startLine("} while (");
conditionGen.add(code, condition);
code.add(");");
} else {
code.startLine("while (");
conditionGen.add(code, condition);
code.add(") {");
makeRegionIndent(code, region.getBody());
code.startLine('}');
}
return code;
}
private void makeSynchronizedRegion(SynchronizedRegion cont, CodeWriter code) throws CodegenException {
code.startLine("synchronized (");
addArg(code, cont.getEnterInsn().getArg(0));
code.add(") {");
makeRegionIndent(code, cont.getRegion());
code.startLine('}');
}
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 (");
addArg(code, arg);
code.add(") {");
code.incIndent();
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(staticField((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.decIndent();
code.startLine('}');
return code;
}
private void makeCaseBlock(IContainer c, CodeWriter code) throws CodegenException {
boolean addBreak = true;
if (RegionUtils.notEmpty(c)) {
makeRegionIndent(code, c);
if (!RegionUtils.hasExitEdge(c)) {
addBreak = false;
}
}
if (addBreak) {
code.startLine().addIndent().add("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) {
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,18 +1,18 @@
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 {
public static String translate(ClassGen clsGen, ArgType type) {
final PrimitiveType stype = type.getPrimitiveType();
if (stype == null)
if (stype == null) {
return type.toString();
}
if (stype == PrimitiveType.OBJECT) {
return clsGen.useClass(type);
}
@@ -35,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;
}
@@ -69,8 +69,9 @@ public class TypeGen {
case OBJECT:
case ARRAY:
if (lit != 0)
if (lit != 0) {
throw new JadxRuntimeException("Wrong object literal: " + type + " = " + lit);
}
return "null";
default:
@@ -100,15 +101,16 @@ public class TypeGen {
public static String formatLong(long lit) {
String l = Long.toString(lit);
if (lit == Long.MIN_VALUE || Math.abs(lit) >= Integer.MAX_VALUE)
if (lit == Long.MIN_VALUE || Math.abs(lit) >= Integer.MAX_VALUE) {
l += "L";
}
return wrapNegNum(lit < 0, l);
}
private static String wrapNegNum(boolean lz, String str) {
if (lz)
return "(" + str + ")";
else
return str;
// 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",
@@ -61,10 +61,11 @@ public class NameMapper {
"void",
"volatile",
"while",
}));
})
);
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 {
@@ -6,8 +6,9 @@ public abstract class AttrNode implements IAttributeNode {
@Override
public AttributesList getAttributes() {
if (attributesList == null)
if (attributesList == null) {
attributesList = new AttributesList();
}
return attributesList;
}
@@ -1,4 +1,4 @@
package jadx.dex.attributes;
package jadx.core.dex.attributes;
public enum AttributeFlag {
TRY_ENTER,
@@ -9,11 +9,19 @@ public enum AttributeFlag {
SYNTHETIC,
BREAK,
RETURN, // block contains only return instruction
DECLARE_VAR,
DONT_WRAP,
DONT_SHRINK,
DONT_GENERATE,
SKIP,
SKIP_FIRST_ARG,
ANONYMOUS_CONSTRUCTOR,
ELSE_IF_CHAIN,
INCONSISTENT_CODE, // warning about incorrect decompilation
}
@@ -1,4 +1,4 @@
package jadx.dex.attributes;
package jadx.core.dex.attributes;
public enum AttributeType {
@@ -19,6 +19,7 @@ public enum AttributeType {
JADX_ERROR(true),
METHOD_INLINE(true),
FIELD_REPLACE(true),
ENUM_CLASS(true),
@@ -27,29 +28,31 @@ public enum AttributeType {
SOURCE_FILE(true),
DECLARE_VARIABLE(true);
// for regions
DECLARE_VARIABLES(true);
private static final int notUniqCount;
private static final int NOT_UNIQ_COUNT;
private final boolean uniq;
private AttributeType(boolean isUniq) {
this.uniq = isUniq;
}
static {
// place all not unique attributes at first
int last = -1;
AttributeType[] vals = AttributeType.values();
for (int i = 0; i < vals.length; i++) {
AttributeType type = vals[i];
if (type.notUniq())
if (type.notUniq()) {
last = i;
}
}
notUniqCount = last + 1;
NOT_UNIQ_COUNT = last + 1;
}
public static int getNotUniqCount() {
return notUniqCount;
}
private AttributeType(boolean isUniq) {
this.uniq = isUniq;
return NOT_UNIQ_COUNT;
}
public boolean isUniq() {
@@ -1,8 +1,8 @@
package jadx.dex.attributes;
package jadx.core.dex.attributes;
import jadx.dex.attributes.annotations.Annotation;
import jadx.dex.attributes.annotations.AnnotationsList;
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;
@@ -10,6 +10,7 @@ import java.util.Collections;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -30,7 +31,7 @@ public final class AttributesList {
public AttributesList() {
flags = EnumSet.noneOf(AttributeFlag.class);
uniqAttr = new EnumMap<AttributeType, IAttribute>(AttributeType.class);
attributes = new ArrayList<IAttribute>(0);
attributes = new LinkedList<IAttribute>();
attrCount = new int[AttributeType.getNotUniqCount()];
}
@@ -51,10 +52,11 @@ public final class AttributesList {
// Attributes
public void add(IAttribute attr) {
if (attr.getType().isUniq())
if (attr.getType().isUniq()) {
uniqAttr.put(attr.getType(), attr);
else
} else {
addMultiAttribute(attr);
}
}
private void addMultiAttribute(IAttribute attr) {
@@ -69,15 +71,17 @@ public final class AttributesList {
public void addAll(AttributesList otherList) {
flags.addAll(otherList.flags);
uniqAttr.putAll(otherList.uniqAttr);
for (IAttribute attr : otherList.attributes)
for (IAttribute attr : otherList.attributes) {
addMultiAttribute(attr);
}
}
public boolean contains(AttributeType type) {
if (type.isUniq())
if (type.isUniq()) {
return uniqAttr.containsKey(type);
else
} else {
return getMultiCountInternal(type) != 0;
}
}
public IAttribute get(AttributeType type) {
@@ -85,9 +89,11 @@ public final class AttributesList {
return uniqAttr.get(type);
} else {
if (getMultiCountInternal(type) != 0) {
for (IAttribute attr : attributes)
if (attr.getType() == type)
for (IAttribute attr : attributes) {
if (attr.getType() == type) {
return attr;
}
}
}
return null;
}
@@ -103,10 +109,7 @@ public final class AttributesList {
public Annotation getAnnotation(String cls) {
AnnotationsList aList = (AnnotationsList) get(AttributeType.ANNOTATION_LIST);
if (aList == null || aList.size() == 0)
return null;
return aList.get(cls);
return aList == null ? null : aList.get(cls);
}
public List<IAttribute> getAll(AttributeType type) {
@@ -118,8 +121,9 @@ public final class AttributesList {
} else {
List<IAttribute> attrs = new ArrayList<IAttribute>(count);
for (IAttribute attr : attributes) {
if (attr.getType() == type)
if (attr.getType() == type) {
attrs.add(attr);
}
}
return attrs;
}
@@ -129,10 +133,11 @@ public final 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)
if (attr.getType() == type) {
it.remove();
}
}
attrCount[type.ordinal()] = 0;
}
@@ -142,13 +147,15 @@ public final class AttributesList {
AttributeType type = attr.getType();
if (type.isUniq()) {
IAttribute a = uniqAttr.get(type);
if (a == attr)
if (a == attr) {
uniqAttr.remove(type);
}
} else {
if (getMultiCountInternal(type) == 0)
if (getMultiCountInternal(type) == 0) {
return;
}
for (Iterator<IAttribute> it = attributes.iterator(); it.hasNext();) {
for (Iterator<IAttribute> it = attributes.iterator(); it.hasNext(); ) {
IAttribute a = it.next();
if (a == attr) {
it.remove();
@@ -167,25 +174,28 @@ public final class AttributesList {
public List<String> getAttributeStrings() {
int size = flags.size() + uniqAttr.size() + attributes.size();
if (size == 0)
if (size == 0) {
return Collections.emptyList();
}
List<String> list = new ArrayList<String>(size);
for (AttributeFlag a : flags)
for (AttributeFlag a : flags) {
list.add(a.toString());
for (IAttribute a : uniqAttr.values())
}
for (IAttribute a : uniqAttr.values()) {
list.add(a.toString());
for (IAttribute a : attributes)
}
for (IAttribute a : attributes) {
list.add(a.toString());
}
return list;
}
@Override
public String toString() {
List<String> list = getAttributeStrings();
if (list.isEmpty())
if (list.isEmpty()) {
return "";
}
return "A:{" + Utils.listToString(list) + "}";
}
@@ -1,8 +1,8 @@
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 final class BlockRegState {
@@ -21,19 +21,19 @@ public final 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) {
@@ -45,9 +45,10 @@ public final class BlockRegState {
StringBuilder str = new StringBuilder();
for (RegisterArg reg : regs) {
if (reg.getTypedVar() != null) {
if (str.length() != 0)
if (str.length() != 0) {
str.append(", ");
str.append(reg.toString());
}
str.append(reg);
}
}
return str.toString();
@@ -0,0 +1,33 @@
package jadx.core.dex.attributes;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.utils.Utils;
import java.util.LinkedList;
import java.util.List;
/**
* List of variables to be declared at region start.
*/
public class DeclareVariablesAttr implements IAttribute {
private final List<RegisterArg> vars = new LinkedList<RegisterArg>();
public Iterable<RegisterArg> getVars() {
return vars;
}
public void addVar(RegisterArg arg) {
vars.add(arg);
}
@Override
public AttributeType getType() {
return AttributeType.DECLARE_VARIABLES;
}
@Override
public String toString() {
return "DECL_VAR: " + Utils.listToString(vars);
}
}
@@ -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;
@@ -18,10 +18,11 @@ public class EnumClassAttr implements IAttribute {
public EnumField(String name, int argsCount) {
this.name = name;
if (argsCount != 0)
if (argsCount != 0) {
this.args = new ArrayList<InsnArg>(argsCount);
else
} else {
this.args = Collections.emptyList();
}
}
public String getName() {
@@ -0,0 +1,32 @@
package jadx.core.dex.attributes;
import jadx.core.dex.info.FieldInfo;
public class FieldReplaceAttr implements IAttribute {
private final FieldInfo fieldInfo;
private final boolean isOuterClass;
public FieldReplaceAttr(FieldInfo fieldInfo, boolean isOuterClass) {
this.fieldInfo = fieldInfo;
this.isOuterClass = isOuterClass;
}
public FieldInfo getFieldInfo() {
return fieldInfo;
}
public boolean isOuterClass() {
return isOuterClass;
}
@Override
public AttributeType getType() {
return AttributeType.FIELD_REPLACE;
}
@Override
public String toString() {
return "REPLACE: " + fieldInfo;
}
}
@@ -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 {
@@ -26,7 +26,7 @@ public class JadxErrorAttr implements IAttribute {
if (cause == null) {
str.append("null");
} else {
str.append(cause.getClass().toString());
str.append(cause.getClass());
str.append(":");
str.append(cause.getMessage());
str.append("\n");
@@ -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 {
@@ -32,18 +32,20 @@ public class JumpAttribute implements IAttribute {
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + dest;
result = prime * result + src;
return result;
return 31 * dest + src;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
JumpAttribute other = (JumpAttribute) obj;
return dest == other.dest && src == other.src;
}
@@ -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,10 +1,13 @@
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.dex.nodes.Edge;
import jadx.core.utils.BlockUtils;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
public class LoopAttr implements IAttribute {
@@ -37,21 +40,39 @@ public class LoopAttr implements IAttribute {
}
/**
* Return block nodes with exit edges from loop <br>
* Return source blocks of exit edges. <br>
* Exit nodes belongs to loop (contains in {@code loopBlocks})
*/
public Set<BlockNode> getExitNodes() {
Set<BlockNode> nodes = new HashSet<BlockNode>();
Set<BlockNode> inloop = getLoopBlocks();
for (BlockNode block : inloop) {
Set<BlockNode> blocks = getLoopBlocks();
for (BlockNode block : blocks) {
// exit: successor node not from this loop, (don't change to getCleanSuccessors)
for (BlockNode s : block.getSuccessors())
if (!inloop.contains(s) && !s.getAttributes().contains(AttributeType.EXC_HANDLER))
for (BlockNode s : block.getSuccessors()) {
if (!blocks.contains(s) && !s.getAttributes().contains(AttributeType.EXC_HANDLER)) {
nodes.add(block);
}
}
}
return nodes;
}
/**
* Return loop exit edges.
*/
public List<Edge> getExitEdges() {
List<Edge> edges = new LinkedList<Edge>();
Set<BlockNode> blocks = getLoopBlocks();
for (BlockNode block : blocks) {
for (BlockNode s : block.getSuccessors()) {
if (!blocks.contains(s) && !s.getAttributes().contains(AttributeType.EXC_HANDLER)) {
edges.add(new Edge(block, s));
}
}
}
return edges;
}
@Override
public String toString() {
return "LOOP: " + start + "->" + end;
@@ -1,6 +1,6 @@
package jadx.dex.attributes;
package jadx.core.dex.attributes;
import jadx.dex.nodes.InsnNode;
import jadx.core.dex.nodes.InsnNode;
public class MethodInlineAttr implements IAttribute {
@@ -1,4 +1,4 @@
package jadx.dex.attributes;
package jadx.core.dex.attributes;
public class SourceFileAttr implements IAttribute {
@@ -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;
@@ -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;
@@ -24,10 +24,11 @@ public class AccessInfo {
}
public AccessInfo remove(int flag) {
if (containsFlag(flag))
if (containsFlag(flag)) {
return new AccessInfo(accFlags - flag, type);
else
} else {
return this;
}
}
public AccessInfo getVisibility() {
@@ -93,80 +94,104 @@ 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 (isPublic())
if (isPublic()) {
code.append("public ");
if (isPrivate())
}
if (isPrivate()) {
code.append("private ");
if (isProtected())
}
if (isProtected()) {
code.append("protected ");
if (isStatic())
}
if (isStatic()) {
code.append("static ");
if (isFinal())
}
if (isFinal()) {
code.append("final ");
if (isAbstract())
}
if (isAbstract()) {
code.append("abstract ");
if (isNative())
}
if (isNative()) {
code.append("native ");
}
switch (type) {
case METHOD:
if ((accFlags & AccessFlags.ACC_SYNCHRONIZED) != 0)
if (isSynchronized()) {
code.append("synchronized ");
if ((accFlags & AccessFlags.ACC_DECLARED_SYNCHRONIZED) != 0)
code.append("synchronized ");
if (isBridge())
}
if (isBridge()) {
code.append("/* bridge */ ");
}
if (Consts.DEBUG) {
if (isVarArgs())
if (isVarArgs()) {
code.append("/* varargs */ ");
}
}
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;
case CLASS:
if ((accFlags & AccessFlags.ACC_STRICT) != 0)
if ((accFlags & AccessFlags.ACC_STRICT) != 0) {
code.append("strict ");
}
if (Consts.DEBUG) {
if ((accFlags & AccessFlags.ACC_SUPER) != 0)
if ((accFlags & AccessFlags.ACC_SUPER) != 0) {
code.append("/* super */ ");
if ((accFlags & AccessFlags.ACC_ENUM) != 0)
}
if ((accFlags & AccessFlags.ACC_ENUM) != 0) {
code.append("/* enum */ ");
}
}
break;
}
if (isSynthetic())
if (isSynthetic()) {
code.append("/* synthetic */ ");
}
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 + " " + accFlags + " (" + makeString() + ")";
return "AccessInfo: " + type + " 0x" + Integer.toHexString(accFlags) + " (" + rawString() + ")";
}
}
@@ -1,9 +1,9 @@
package jadx.dex.info;
package jadx.core.dex.info;
import jadx.Consts;
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.Map;
@@ -13,14 +13,28 @@ public final class ClassInfo {
private static final Map<ArgType, ClassInfo> CLASSINFO_CACHE = new WeakHashMap<ArgType, ClassInfo>();
private final ArgType type;
private String pkg;
private String name;
private String fullName;
// for inner class not equals null
private ClassInfo parentClass;
private ClassInfo(ArgType type) {
assert type.isObject() : "Not class type: " + type;
this.type = type;
splitNames(true);
}
public static ClassInfo fromDex(DexNode dex, int clsIndex) {
if (clsIndex == DexNode.NO_INDEX)
if (clsIndex == DexNode.NO_INDEX) {
return null;
}
ArgType type = dex.getType(clsIndex);
if (type.isArray())
if (type.isArray()) {
type = ArgType.OBJECT;
}
return fromType(type);
}
@@ -41,54 +55,41 @@ public final class ClassInfo {
CLASSINFO_CACHE.clear();
}
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 : "Raw type: " + type;
String name;
String clsName;
int dot = fullObjectName.lastIndexOf('.');
if (dot == -1) {
// rename default package if it used from class with package (often for obfuscated apps),
pkg = Consts.DEFAULT_PACKAGE_NAME;
name = fullObjectName;
clsName = fullObjectName;
} else {
pkg = fullObjectName.substring(0, dot);
name = fullObjectName.substring(dot + 1);
clsName = fullObjectName.substring(dot + 1);
}
int sep = name.lastIndexOf('$');
if (canBeInner && sep > 0 && sep != name.length() - 1) {
String parClsName = pkg + '.' + name.substring(0, sep);
int sep = clsName.lastIndexOf('$');
if (canBeInner && sep > 0 && sep != clsName.length() - 1) {
String parClsName = pkg + "." + clsName.substring(0, sep);
parentClass = fromName(parClsName);
name = name.substring(sep + 1);
clsName = clsName.substring(sep + 1);
} else {
parentClass = null;
}
char firstChar = name.charAt(0);
char firstChar = clsName.charAt(0);
if (Character.isDigit(firstChar)) {
name = "InnerClass_" + name;
} else if(firstChar == '$') {
name = "_" + name;
clsName = Consts.ANONYMOUS_CLASS_PREFIX + clsName;
} else if (firstChar == '$') {
clsName = "_" + clsName;
}
if (NameMapper.isReserved(name)) {
name += "_";
if (NameMapper.isReserved(clsName)) {
clsName += "_";
}
this.fullName = (parentClass != null ? parentClass.getFullName() : pkg) + "." + name;
this.name = name;
this.fullName = (parentClass != null ? parentClass.getFullName() : pkg) + "." + clsName;
this.name = clsName;
}
public String getFullPath() {
@@ -101,10 +102,18 @@ public final class ClassInfo {
return fullName;
}
public boolean isObject() {
return fullName.equals(Consts.CLASS_OBJECT);
}
public String getShortName() {
return name;
}
public String getRawName() {
return type.getObject();
}
public String getPackage() {
return pkg;
}
@@ -135,17 +144,19 @@ public final class ClassInfo {
@Override
public String toString() {
return getFullName();
return fullName;
}
@Override
public int hashCode() {
return type.hashCode();
return fullName.hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (this == obj) {
return true;
}
if (obj instanceof ClassInfo) {
ClassInfo other = (ClassInfo) obj;
return this.getFullName().equals(other.getFullName());
@@ -0,0 +1,77 @@
package jadx.core.dex.info;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.DexNode;
import com.android.dx.io.FieldId;
public class FieldInfo {
private final ClassInfo declClass;
private final String name;
private final ArgType type;
public FieldInfo(ClassInfo declClass, String name, ArgType type) {
this.declClass = declClass;
this.name = name;
this.type = type;
}
public static FieldInfo fromDex(DexNode dex, int index) {
FieldId field = dex.getFieldId(index);
return new FieldInfo(
ClassInfo.fromDex(dex, field.getDeclaringClassIndex()),
dex.getString(field.getNameIndex()),
dex.getType(field.getTypeIndex()));
}
public static String getNameById(DexNode dex, int ind) {
return dex.getString(dex.getFieldId(ind).getNameIndex());
}
public String getName() {
return name;
}
public ArgType getType() {
return type;
}
public ClassInfo getDeclClass() {
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;
@@ -18,10 +18,6 @@ public final class MethodInfo {
private final ClassInfo declClass;
private final String shortId;
public static MethodInfo fromDex(DexNode dex, int mthIndex) {
return new MethodInfo(dex, mthIndex);
}
private MethodInfo(DexNode dex, int mthIndex) {
MethodId mthId = dex.getMethodId(mthIndex);
name = dex.getString(mthId.getNameIndex());
@@ -31,14 +27,20 @@ public final class MethodInfo {
retType = dex.getType(proto.getReturnTypeIndex());
args = dex.readParamList(proto.getParametersOffset());
StringBuilder strArg = new StringBuilder();
strArg.append('(');
for (ArgType arg : args)
strArg.append(TypeGen.signature(arg));
strArg.append(')');
// strArg.append(TypeGen.signature(retType));
StringBuilder signature = new StringBuilder();
signature.append(name);
signature.append('(');
for (ArgType arg : args) {
signature.append(TypeGen.signature(arg));
}
signature.append(')');
signature.append(TypeGen.signature(retType));
shortId = name + strArg;
shortId = signature.toString();
}
public static MethodInfo fromDex(DexNode dex, int mthIndex) {
return new MethodInfo(dex, mthIndex);
}
public String getName() {
@@ -49,10 +51,10 @@ public final class MethodInfo {
return declClass.getFullName() + "." + name;
}
public String getFullId() {
return declClass.getFullName() + "." + shortId;
}
/**
* Method name and signature
*/
@@ -86,30 +88,40 @@ public final class MethodInfo {
@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();
int result = declClass.hashCode();
result = 31 * result + retType.hashCode();
result = 31 * 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;
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;
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
+ "(" + Utils.listToString(args) + ")";
return declClass.getFullName() + "." + name
+ "(" + Utils.listToString(args) + "):" + retType;
}
}
@@ -1,10 +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.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;
@@ -51,7 +51,7 @@ public class ArithNode extends InsnNode {
}
public ArithNode(ArithOp op, RegisterArg res, InsnArg a) {
super(InsnType.ARITH, 1);
super(InsnType.ARITH_ONEARG, 1);
this.op = op;
setResult(res);
addArg(a);
@@ -1,4 +1,4 @@
package jadx.dex.instructions;
package jadx.core.dex.instructions;
public enum ArithOp {
ADD("+"),
@@ -15,12 +15,12 @@ public enum ArithOp {
SHR(">>"),
USHR(">>>");
private final String symbol;
private ArithOp(String symbol) {
this.symbol = symbol;
}
private final String symbol;
public String getSymbol() {
return this.symbol;
}
@@ -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;
}
}
}
@@ -1,18 +1,18 @@
package jadx.dex.instructions;
package jadx.core.dex.instructions;
import jadx.dex.nodes.InsnNode;
import jadx.utils.InsnUtils;
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);
this(InsnType.GOTO, target, 0);
}
protected GotoNode(InsnType type, int target) {
super(type);
protected GotoNode(InsnType type, int target, int argsCount) {
super(type, argsCount);
this.target = target;
}
@@ -0,0 +1,80 @@
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.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 {
private static final ArgType ARG_TYPE = ArgType.unknown(
PrimitiveType.INT, PrimitiveType.OBJECT, PrimitiveType.ARRAY,
PrimitiveType.BOOLEAN, PrimitiveType.SHORT, PrimitiveType.CHAR);
protected IfOp op;
private BlockNode thenBlock;
private BlockNode elseBlock;
public IfNode(DecodedInstruction insn, IfOp op) {
this(op, insn.getTarget(),
InsnArg.reg(insn, 0, ARG_TYPE),
insn.getRegisterCount() == 1 ? InsnArg.lit(0, ARG_TYPE) : InsnArg.reg(insn, 1, ARG_TYPE));
}
public IfNode(IfOp op, int targetOffset, InsnArg arg1, InsnArg arg2) {
super(InsnType.IF, targetOffset, 2);
this.op = op;
addArg(arg1);
addArg(arg2);
}
public IfOp getOp() {
return op;
}
public void invertCondition() {
op = op.invert();
BlockNode tmp = thenBlock;
thenBlock = elseBlock;
elseBlock = tmp;
target = thenBlock.getStartOffset();
}
public void changeCondition(IfOp op, InsnArg arg1, InsnArg arg2) {
this.op = op;
setArg(0, arg1);
setArg(1, arg2);
}
public void initBlocks(BlockNode curBlock) {
thenBlock = getBlockByOffset(target, curBlock.getSuccessors());
if (curBlock.getSuccessors().size() == 1) {
elseBlock = thenBlock;
} else {
elseBlock = selectOther(thenBlock, curBlock.getSuccessors());
}
}
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() + " " + 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);
}
}
}
@@ -1,7 +1,7 @@
package jadx.dex.instructions;
package jadx.core.dex.instructions;
import jadx.dex.nodes.InsnNode;
import jadx.utils.InsnUtils;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.utils.InsnUtils;
public class IndexInsnNode extends InsnNode {
@@ -1,15 +1,16 @@
package jadx.dex.instructions;
package jadx.core.dex.instructions;
import jadx.dex.info.FieldInfo;
import jadx.dex.info.MethodInfo;
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;
@@ -73,7 +74,7 @@ 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:
@@ -95,13 +96,13 @@ public class InsnDecoder {
case Opcodes.CONST_STRING:
case Opcodes.CONST_STRING_JUMBO: {
InsnNode node = new IndexInsnNode(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(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;
}
@@ -145,7 +146,7 @@ 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:
@@ -188,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:
@@ -224,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:
@@ -236,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:
@@ -252,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:
@@ -291,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);
@@ -387,7 +388,8 @@ 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(InsnType.RETURN, 0);
@@ -471,7 +473,7 @@ public class InsnDecoder {
case Opcodes.ARRAY_LENGTH: {
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)));
node.addArg(InsnArg.reg(insn, 1, ArgType.array(ArgType.UNKNOWN)));
return node;
}
@@ -569,19 +571,23 @@ 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++)
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++) {
@@ -593,7 +599,7 @@ public class InsnDecoder {
private InsnNode fillArray(DecodedInstruction insn) {
DecodedInstruction payload = insnArr[insn.getTarget()];
return new FillArrayOp(insn.getA(), (FillArrayDataPayloadDecodedInstruction) payload);
return new FillArrayNode(insn.getA(), (FillArrayDataPayloadDecodedInstruction) payload);
}
private InsnNode filledNewArray(DecodedInstruction insn, int offset, boolean isRange) {
@@ -608,8 +614,9 @@ public class InsnDecoder {
r++;
}
} else {
for (int i = 0; i < insn.getRegisterCount(); i++)
for (int i = 0; i < insn.getRegisterCount(); i++) {
regs[i] = InsnArg.reg(insn, i, elType);
}
}
return insn(InsnType.FILLED_NEW_ARRAY,
resReg == -1 ? null : InsnArg.reg(resReg, arrType),
@@ -657,7 +664,7 @@ public class InsnDecoder {
return new ArithNode(insn, op, type, false);
}
private InsnNode arith_lit(DecodedInstruction insn, ArithOp op, ArgType type) {
private InsnNode arithLit(DecodedInstruction insn, ArithOp op, ArgType type) {
return new ArithNode(insn, op, type, true);
}
@@ -668,13 +675,28 @@ public class InsnDecoder {
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(type, args == null ? 0 : args.length);
inode.setResult(res);
if (args != null)
for (InsnArg arg : args)
inode.addArg(arg);
return inode;
InsnNode node = new InsnNode(type, args == null ? 0 : args.length);
node.setResult(res);
if (args != null) {
for (InsnArg arg : args) {
node.addArg(arg);
}
}
return node;
}
private int getMoveResultRegister(DecodedInstruction[] insnArr, int offset) {
@@ -693,19 +715,23 @@ public class InsnDecoder {
public static int getPrevInsnOffset(Object[] insnArr, int offset) {
int i = offset - 1;
while (i >= 0 && insnArr[i] == null)
while (i >= 0 && insnArr[i] == null) {
i--;
if (i < 0)
}
if (i < 0) {
return -1;
}
return i;
}
public static int getNextInsnOffset(Object[] insnArr, int offset) {
int i = offset + 1;
while (i < insnArr.length && insnArr[i] == null)
while (i < insnArr.length && insnArr[i] == null) {
i++;
if (i >= insnArr.length)
}
if (i >= insnArr.length) {
return -1;
}
return i;
}
}
@@ -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,
@@ -52,6 +54,7 @@ public enum InsnType {
CONTINUE,
STR_CONCAT, // strings concatenation
ARITH_ONEARG,
TERNARY,
ARGS, // just generate arguments
@@ -1,11 +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.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;
@@ -19,8 +19,9 @@ public class InvokeNode extends InsnNode {
this.mth = mth;
this.type = type;
if (resReg >= 0)
if (resReg >= 0) {
setResult(InsnArg.reg(resReg, mth.getReturnType()));
}
int k = isRange ? insn.getA() : 0;
if (type != InvokeType.STATIC) {
@@ -1,4 +1,4 @@
package jadx.dex.instructions;
package jadx.core.dex.instructions;
public enum InvokeType {
STATIC,
@@ -1,18 +1,18 @@
package jadx.dex.instructions;
package jadx.core.dex.instructions;
import jadx.dex.instructions.args.InsnArg;
import jadx.dex.nodes.InsnNode;
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(InsnArg arg, int[] keys, int[] targets, int def) {
public SwitchNode(InsnArg arg, Object[] keys, int[] targets, int def) {
super(InsnType.SWITCH, 1);
this.keys = keys;
this.targets = targets;
@@ -24,7 +24,7 @@ public class SwitchNode extends InsnNode {
return keys.length;
}
public int[] getKeys() {
public Object[] getKeys() {
return keys;
}
@@ -42,8 +42,9 @@ public class SwitchNode extends InsnNode {
targ.append('[');
for (int i = 0; i < targets.length; i++) {
targ.append(InsnUtils.formatOffset(targets[i]));
if (i < targets.length - 1)
if (i < targets.length - 1) {
targ.append(", ");
}
}
targ.append(']');
return super.toString() + " k:" + Arrays.toString(keys) + " t:" + targ;
@@ -1,20 +1,15 @@
package jadx.dex.instructions.args;
package jadx.core.dex.instructions.args;
import jadx.Consts;
import jadx.utils.Utils;
import jadx.core.Consts;
import jadx.core.clsp.ClspGraph;
import jadx.core.dex.nodes.parser.SignatureParser;
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);
@@ -39,28 +34,50 @@ public abstract class ArgType {
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);
return new ObjectType(obj);
}
public static ArgType genericType(String type) {
return new GenericTypeArg(type);
return new GenericType(type);
}
public static ArgType wildcard() {
return new WildcardType(OBJECT, 0);
}
public static ArgType wildcard(ArgType obj, int bound) {
return new WildcardType(obj, bound);
}
public static ArgType generic(String sign) {
return parseSignature(sign);
return new SignatureParser(sign).consumeType();
}
public static ArgType generic(String obj, ArgType[] generics) {
return new GenericObjectArg(obj, generics);
return new GenericObject(obj, generics);
}
public static ArgType genericInner(ArgType genericType, String innerName, ArgType[] generics) {
return new GenericObject((GenericObject) genericType, innerName, generics);
}
public static ArgType array(ArgType vtype) {
@@ -71,14 +88,14 @@ public abstract class ArgType {
return new UnknownArg(types);
}
private static abstract class KnownTypeArg extends ArgType {
private abstract static class KnownType extends ArgType {
@Override
public boolean isTypeKnown() {
return true;
}
}
private static final class PrimitiveArg extends KnownTypeArg {
private static final class PrimitiveArg extends KnownType {
private final PrimitiveType type;
public PrimitiveArg(PrimitiveType type) {
@@ -96,18 +113,23 @@ public abstract class ArgType {
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 static class ObjectType extends KnownType {
private final String object;
public ObjectArg(String obj) {
public ObjectType(String obj) {
this.object = Utils.cleanObjectName(obj);
this.hash = obj.hashCode();
this.hash = object.hashCode();
}
@Override
@@ -125,14 +147,19 @@ public abstract class ArgType {
return PrimitiveType.OBJECT;
}
@Override
boolean internalEquals(Object obj) {
return object.equals(((ObjectType) obj).object);
}
@Override
public String toString() {
return object;
}
}
private static final class GenericTypeArg extends ObjectArg {
public GenericTypeArg(String obj) {
private static final class GenericType extends ObjectType {
public GenericType(String obj) {
super(obj);
}
@@ -142,27 +169,102 @@ public abstract class ArgType {
}
}
private static final class GenericObjectArg extends ObjectArg {
private final ArgType[] generics;
private static final class WildcardType extends ObjectType {
private final ArgType type;
private final int bounds;
public GenericObjectArg(String obj, ArgType[] generics) {
public WildcardType(ArgType obj, int bound) {
super(ArgType.OBJECT.getObject());
this.type = obj;
this.bounds = bound;
}
@Override
public boolean isGeneric() {
return true;
}
@Override
public ArgType getWildcardType() {
return type;
}
/**
* Return wildcard bounds:
* <ul>
* <li> 1 for upper bound (? extends A) </li>
* <li> 0 no bounds (?) </li>
* <li>-1 for lower bound (? super A) </li>
* </ul>
*/
@Override
public int getWildcardBounds() {
return bounds;
}
@Override
boolean internalEquals(Object obj) {
return super.internalEquals(obj)
&& bounds == ((WildcardType) obj).bounds
&& type.equals(((WildcardType) obj).type);
}
@Override
public String toString() {
if (bounds == 0) {
return "?";
}
return "? " + (bounds == -1 ? "super" : "extends") + " " + type.toString();
}
}
private static class GenericObject extends ObjectType {
private final ArgType[] generics;
private final GenericObject outerType;
public GenericObject(String obj, ArgType[] generics) {
super(obj);
this.outerType = null;
this.generics = generics;
this.hash = obj.hashCode() + 31 * Arrays.hashCode(generics);
}
public GenericObject(GenericObject outerType, String innerName, ArgType[] generics) {
super(outerType.getObject() + "$" + innerName);
this.outerType = outerType;
this.generics = generics;
this.hash = outerType.hashCode() + 31 * innerName.hashCode()
+ 31 * 31 * Arrays.hashCode(generics);
}
@Override
public boolean isGeneric() {
return true;
}
@Override
public ArgType[] getGenericTypes() {
return generics;
}
@Override
public ArgType getOuterType() {
return outerType;
}
@Override
boolean internalEquals(Object obj) {
return super.internalEquals(obj)
&& Arrays.equals(generics, ((GenericObject) obj).generics);
}
@Override
public String toString() {
return super.toString() + "<" + Utils.arrayToString(generics) + ">";
}
}
private static final class ArrayArg extends KnownTypeArg {
private static final class ArrayArg extends KnownType {
private final ArgType arrayElement;
public ArrayArg(ArgType arrayElement) {
@@ -195,14 +297,19 @@ public abstract class ArgType {
return arrayElement.getArrayRootElement();
}
@Override
boolean internalEquals(Object obj) {
return arrayElement.equals(((ArrayArg) obj).arrayElement);
}
@Override
public String toString() {
return arrayElement.toString() + "[]";
return arrayElement + "[]";
}
}
private static final class UnknownArg extends ArgType {
private final PrimitiveType possibleTypes[];
private final PrimitiveType[] possibleTypes;
public UnknownArg(PrimitiveType[] types) {
this.possibleTypes = types;
@@ -221,27 +328,36 @@ public abstract class ArgType {
@Override
public boolean contains(PrimitiveType type) {
for (PrimitiveType t : possibleTypes)
if (t == 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)
if (f == PrimitiveType.OBJECT || f == PrimitiveType.ARRAY) {
return object(Consts.CLASS_OBJECT);
else
} 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)
if (possibleTypes.length == PrimitiveType.values().length) {
return "?";
else
} else {
return "?" + Arrays.toString(possibleTypes);
}
}
}
@@ -258,13 +374,17 @@ public abstract class ArgType {
}
public String getObject() {
throw new UnsupportedOperationException();
throw new UnsupportedOperationException("ArgType.getObject(), call class: " + this.getClass());
}
public boolean isObject() {
return false;
}
public boolean isGeneric() {
return false;
}
public boolean isGenericType() {
return false;
}
@@ -273,6 +393,21 @@ public abstract class ArgType {
return null;
}
public ArgType getWildcardType() {
return null;
}
/**
* @see jadx.core.dex.instructions.args.ArgType.WildcardType#getWildcardBounds()
*/
public int getWildcardBounds() {
return 0;
}
public ArgType getOuterType() {
return null;
}
public boolean isArray() {
return false;
}
@@ -302,77 +437,108 @@ public abstract class ArgType {
}
public static ArgType merge(ArgType a, ArgType b) {
if (a == b)
return a;
if (b == null || a == null)
if (b == null || a == null) {
return null;
}
if (a.equals(b)) {
return a;
}
ArgType res = mergeInternal(a, b);
if (res == null)
if (res == null) {
res = mergeInternal(b, a); // swap
}
return res;
}
private static ArgType mergeInternal(ArgType a, ArgType b) {
if (a == UNKNOWN)
if (a == UNKNOWN) {
return b;
}
if (!a.isTypeKnown()) {
if (b.isTypeKnown()) {
if (a.contains(b.getPrimitiveType()))
if (a.contains(b.getPrimitiveType())) {
return b;
else
} else {
return null;
}
} else {
// both types unknown
List<PrimitiveType> types = new ArrayList<PrimitiveType>();
for (PrimitiveType type : a.getPossibleTypes()) {
if (b.contains(type))
if (b.contains(type)) {
types.add(type);
}
}
if (types.size() == 0) {
if (types.isEmpty()) {
return null;
} else if (types.size() == 1) {
PrimitiveType nt = types.get(0);
if (nt == PrimitiveType.OBJECT || nt == PrimitiveType.ARRAY)
if (nt == PrimitiveType.OBJECT || nt == PrimitiveType.ARRAY) {
return unknown(nt);
else
} 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()) {
if (a.getObject().equals(b.getObject())) {
if (a.getGenericTypes() != null)
return a;
else
return b;
} else if (a.getObject().equals(OBJECT.getObject()))
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 (b.getObject().equals(OBJECT.getObject()))
} else if (bObj.equals(Consts.CLASS_OBJECT)) {
return a;
else
} 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.isArray() && b.isArray()) {
ArgType res = merge(a.getArrayElement(), b.getArrayElement());
return (res == null ? null : ArgType.array(res));
}
if (a.isPrimitive() && b.isPrimitive()) {
if (a.getRegCount() == b.getRegCount())
// return primitive(PrimitiveType.getWidest(a.getPrimitiveType(), b.getPrimitiveType()));
return primitive(PrimitiveType.getSmaller(a.getPrimitiveType(), b.getPrimitiveType()));
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) {
@@ -387,146 +553,7 @@ public abstract class ArgType {
}
}
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) {
public static ArgType parse(char f) {
switch (f) {
case 'Z':
return BOOLEAN;
@@ -553,8 +580,14 @@ public abstract class ArgType {
public int getRegCount() {
if (isPrimitive()) {
PrimitiveType type = getPrimitiveType();
if (type == PrimitiveType.LONG || type == PrimitiveType.DOUBLE)
if (type == PrimitiveType.LONG || type == PrimitiveType.DOUBLE) {
return 2;
} else {
return 1;
}
}
if (!isTypeKnown()) {
return 0;
}
return 1;
}
@@ -569,13 +602,22 @@ public abstract class ArgType {
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;
// TODO: don't use toString
return toString().equals(obj.toString());
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;
}
}
@@ -0,0 +1,121 @@
package jadx.core.dex.instructions.args;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.utils.InsnUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.android.dx.io.instructions.DecodedInstruction;
/**
* Instruction argument,
* argument can be register, literal or instruction
*/
public abstract class InsnArg extends Typed {
private static final Logger LOG = LoggerFactory.getLogger(InsnArg.class);
protected InsnNode parentInsn;
public static RegisterArg reg(int regNum, ArgType type) {
return new RegisterArg(regNum, type);
}
public static RegisterArg reg(DecodedInstruction insn, int argNum, ArgType type) {
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);
}
public static LiteralArg lit(DecodedInstruction insn, ArgType type) {
return lit(insn.getLiteral(), type);
}
private static InsnWrapArg wrap(InsnNode insn) {
return new InsnWrapArg(insn);
}
public boolean isRegister() {
return false;
}
public boolean isLiteral() {
return false;
}
public boolean isInsnWrap() {
return false;
}
public boolean isNamed() {
return false;
}
public boolean isField() {
return false;
}
public InsnNode getParentInsn() {
return parentInsn;
}
public void setParentInsn(InsnNode parentInsn) {
this.parentInsn = parentInsn;
}
public InsnArg wrapInstruction(InsnNode insn) {
InsnNode parent = parentInsn;
if (parent == null) {
return null;
}
if (parent == insn) {
LOG.debug("Can't wrap instruction info itself: " + insn);
return null;
}
int count = parent.getArgsCount();
for (int i = 0; i < count; i++) {
if (parent.getArg(i) == this) {
InsnArg arg = wrapArg(insn);
parent.setArg(i, arg);
return arg;
}
}
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,14 +1,14 @@
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;
public InsnWrapArg(InsnNode insn) {
ArgType type = (insn.getResult() == null ? ArgType.VOID : insn.getResult().getType());
this.typedVar = new TypedVar(type);
RegisterArg result = insn.getResult();
this.typedVar = new TypedVar((result != null ? result.getType() : ArgType.VOID));
this.wrappedInsn = insn;
}
@@ -0,0 +1,78 @@
package jadx.core.dex.instructions.args;
import jadx.core.codegen.TypeGen;
import jadx.core.utils.exceptions.JadxRuntimeException;
public final class LiteralArg extends InsnArg {
public static final LiteralArg TRUE = new LiteralArg(1, ArgType.BOOLEAN);
public static final LiteralArg FALSE = new LiteralArg(0, ArgType.BOOLEAN);
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 int hashCode() {
return (int) (literal ^ (literal >>> 32)) + 31 * getType().hashCode();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
LiteralArg that = (LiteralArg) o;
return literal == that.literal && getType().equals(that.getType());
}
@Override
public String toString() {
try {
String value = TypeGen.literalToString(literal, getType());
if (getType().equals(ArgType.BOOLEAN) && (value.equals("true") || value.equals("false"))) {
return value;
}
return "(" + value + " " + 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"),
@@ -30,17 +30,19 @@ public enum PrimitiveType {
}
public static PrimitiveType getWidest(PrimitiveType a, PrimitiveType b) {
if (a.ordinal() > b.ordinal())
if (a.ordinal() > b.ordinal()) {
return a;
else
} else {
return b;
}
}
public static PrimitiveType getSmaller(PrimitiveType a, PrimitiveType b) {
if (a.ordinal() < b.ordinal())
if (a.ordinal() < b.ordinal()) {
return a;
else
} else {
return b;
}
}
@Override
@@ -0,0 +1,130 @@
package jadx.core.dex.instructions.args;
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) {
this.regNum = rn;
}
public RegisterArg(int rn, ArgType type) {
this.typedVar = new TypedVar(type);
this.regNum = rn;
}
public int getRegNum() {
return regNum;
}
@Override
public boolean isRegister() {
return true;
}
public InsnNode getAssignInsn() {
for (InsnArg arg : getTypedVar().getUseList()) {
InsnNode assignInsn = arg.getParentInsn();
if (assignInsn == null) {
// assign as function argument
return null;
} else if (assignInsn.getResult() != null
&& assignInsn.getResult().getRegNum() == regNum) {
return assignInsn;
}
}
return null;
}
/**
* Return constant value from register assign or null if not constant
*
* @return LiteralArg, String or ArgType
*/
public Object getConstValue(DexNode dex) {
InsnNode parInsn = getAssignInsn();
if (parInsn != null) {
InsnType insnType = parInsn.getType();
switch (insnType) {
case CONST:
return parInsn.getArg(0);
case CONST_STR:
return ((ConstStringNode) parInsn).getString();
case CONST_CLASS:
return ((ConstClassNode) parInsn).getClsType();
case SGET:
FieldInfo f = (FieldInfo) ((IndexInsnNode) parInsn).getIndex();
FieldNode fieldNode = dex.resolveField(f);
if (fieldNode != null) {
FieldValueAttr attr = (FieldValueAttr) fieldNode.getAttributes().get(AttributeType.FIELD_VALUE);
if (attr != null) {
return attr.getValue();
}
} else {
LOG.warn("Field {} not found in dex {}", f, dex);
}
break;
}
}
return null;
}
@Override
public boolean isThis() {
if (isRegister()) {
String name = getTypedVar().getName();
if ("this".equals(name)) {
return true;
}
// maybe it was moved from 'this' register
InsnNode ai = getAssignInsn();
if (ai != null && ai.getType() == InsnType.MOVE) {
InsnArg arg = ai.getArg(0);
if (arg != this && arg.isThis()) {
return true;
}
}
}
return false;
}
@Override
public int hashCode() {
return regNum * 31 + typedVar.hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
RegisterArg other = (RegisterArg) obj;
return regNum == other.regNum && typedVar.equals(other.typedVar);
}
@Override
public String toString() {
return "(r" + regNum + " " + typedVar + ")";
}
}
@@ -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,6 +1,7 @@
package jadx.dex.instructions.args;
package jadx.core.dex.instructions.args;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class TypedVar {
@@ -20,13 +21,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) {
@@ -43,10 +39,6 @@ public class TypedVar {
}
}
public List<InsnArg> getUseList() {
return useList;
}
public String getName() {
return name;
}
@@ -55,6 +47,33 @@ public class TypedVar {
this.name = name;
}
public List<InsnArg> getUseList() {
return useList;
}
public void removeUse(InsnArg arg) {
Iterator<InsnArg> it = useList.iterator();
while (it.hasNext()) {
InsnArg use = it.next();
if (use == arg) {
it.remove();
}
}
}
public void mergeName(TypedVar arg) {
String argName = arg.getName();
if (argName != null) {
setName(argName);
} else if (getName() != null) {
arg.setName(getName());
}
}
public boolean isImmutable() {
return false;
}
@Override
public int hashCode() {
return type.hashCode() * 31 + (name == null ? 0 : name.hashCode());
@@ -62,13 +81,23 @@ public class TypedVar {
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof TypedVar)) {
return false;
}
TypedVar other = (TypedVar) obj;
if (!type.equals(other.type)) return false;
if (!type.equals(other.type)) {
return false;
}
if (name == null) {
if (other.name != null) return false;
if (other.name != null) {
return false;
}
} else if (!name.equals(other.name)) {
return false;
}
@@ -78,8 +107,9 @@ public class TypedVar {
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
if(name != null)
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,30 +21,28 @@ public class ConstructorInsn extends InsnNode {
SELF // call itself
}
private CallType callType;
public ConstructorInsn(MethodNode mth, InvokeNode invoke) {
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())) {
if (callMth.getShortId().equals(mth.getMethodInfo().getShortId())) {
// self constructor
callType = CallType.SELF;
} else if (mth.getMethodInfo().isConstructor()) {
} else {
callType = CallType.THIS;
}
} else {
callType = CallType.SUPER;
}
}
if (callType == null) {
} else {
callType = CallType.CONSTRUCTOR;
setResult((RegisterArg) invoke.getArg(0));
setResult(instanceArg);
}
for (int i = 1; i < invoke.getArgsCount(); i++) {
addArg(invoke.getArg(i));
}
@@ -53,6 +53,10 @@ public class ConstructorInsn extends InsnNode {
return callMth;
}
public RegisterArg getInstanceArg() {
return instanceArg;
}
public ClassInfo getClassType() {
return callMth.getDeclClass();
}
@@ -0,0 +1,42 @@
package jadx.core.dex.instructions.mods;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.LiteralArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.regions.IfCondition;
import jadx.core.utils.InsnUtils;
import jadx.core.utils.Utils;
public class TernaryInsn extends InsnNode {
private final IfCondition condition;
public TernaryInsn(IfCondition condition, RegisterArg result, InsnArg th, InsnArg els) {
super(InsnType.TERNARY, 2);
setResult(result);
if (th.equals(LiteralArg.FALSE) && els.equals(LiteralArg.TRUE)) {
// inverted
this.condition = IfCondition.invert(condition);
addArg(els);
addArg(th);
} else {
this.condition = condition;
addArg(th);
addArg(els);
}
}
public IfCondition getCondition() {
return condition;
}
@Override
public String toString() {
return InsnUtils.formatOffset(offset) + ": TERNARY"
+ getResult() + " = "
+ Utils.listToString(getArguments());
}
}
@@ -1,10 +1,11 @@
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.AttributeFlag;
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;
@@ -21,8 +22,13 @@ public class BlockNode extends AttrNode implements IBlock {
private List<BlockNode> successors = new ArrayList<BlockNode>(1);
private List<BlockNode> cleanSuccessors;
private BitSet doms; // all dominators
private BlockNode idom; // immediate dominator
// all dominators
private BitSet doms;
// dominance frontier
private BitSet domFrontier;
// immediate dominator
private BlockNode idom;
// blocks on which dominates this block
private final List<BlockNode> dominatesOn = new ArrayList<BlockNode>(1);
private BlockRegState startState;
@@ -72,20 +78,22 @@ public class BlockNode extends AttrNode implements IBlock {
LoopAttr loop = (LoopAttr) block.getAttributes().get(AttributeType.LOOP);
if (loop == null) {
for (BlockNode b : sucList) {
if (!b.getAttributes().contains(AttributeType.EXC_HANDLER))
if (!b.getAttributes().contains(AttributeType.EXC_HANDLER)) {
nodes.add(b);
}
}
} else {
for (BlockNode b : sucList) {
if (!b.getAttributes().contains(AttributeType.EXC_HANDLER)) {
// don't follow back edge
if (loop.getStart() == b && loop.getEnd() == block)
if (loop.getStart() == b && loop.getEnd() == block) {
continue;
}
nodes.add(b);
}
}
}
return (nodes.size() == sucList.size() ? sucList : nodes);
return nodes.size() == sucList.size() ? sucList : nodes;
}
@Override
@@ -115,6 +123,14 @@ public class BlockNode extends AttrNode implements IBlock {
this.doms = doms;
}
public BitSet getDomFrontier() {
return domFrontier;
}
public void setDomFrontier(BitSet domFrontier) {
this.domFrontier = domFrontier;
}
/**
* Immediate dominator
*/
@@ -146,6 +162,14 @@ public class BlockNode extends AttrNode implements IBlock {
this.endState = endState;
}
public boolean isSynthetic() {
return getAttributes().contains(AttributeFlag.SYNTHETIC);
}
public boolean isReturnBlock() {
return getAttributes().contains(AttributeFlag.RETURN);
}
@Override
public int hashCode() {
return id; // TODO id can change during reindex
@@ -153,13 +177,25 @@ public class BlockNode extends AttrNode implements IBlock {
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (hashCode() != obj.hashCode()) return false;
if (!(obj instanceof BlockNode)) return false;
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (hashCode() != obj.hashCode()) {
return false;
}
if (!(obj instanceof BlockNode)) {
return false;
}
BlockNode other = (BlockNode) obj;
if (id != other.id) return false;
if (startOffset != other.startOffset) return false;
if (id != other.id) {
return false;
}
if (startOffset != other.startOffset) {
return false;
}
return true;
}
@@ -0,0 +1,424 @@
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.SignatureParser;
import jadx.core.dex.nodes.parser.StaticValuesParser;
import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.exceptions.JadxRuntimeException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
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 final AccessInfo accessFlags;
private ClassInfo superClass;
private List<ClassInfo> interfaces;
private Map<ArgType, List<ArgType>> genericMap;
private final List<MethodNode> methods;
private final List<FieldNode> fields;
private Map<Object, FieldNode> constFields = Collections.emptyMap();
private List<ClassNode> innerClasses = Collections.emptyList();
// store decompiled code
private CodeWriter code;
// store parent for inner classes or 'this' otherwise
private ClassNode parentClass;
public ClassNode(DexNode dex, ClassDef cls) throws DecodeException {
this.dex = dex;
this.clsInfo = ClassInfo.fromDex(dex, cls.getTypeIndex());
try {
if (cls.getSupertypeIndex() == DexNode.NO_INDEX) {
this.superClass = null;
} else {
this.superClass = 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);
int mthsCount = clsData.getDirectMethods().length + clsData.getVirtualMethods().length;
int fieldsCount = clsData.getStaticFields().length + clsData.getInstanceFields().length;
methods = new ArrayList<MethodNode>(mthsCount);
fields = new ArrayList<FieldNode>(fieldsCount);
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));
}
} else {
methods = Collections.emptyList();
fields = Collections.emptyList();
}
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);
}
}
// restore original access flags from dalvik annotation if present
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).parse(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));
int count = parser.processFields(staticFields);
constFields = new LinkedHashMap<Object, FieldNode>(count);
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);
}
}
}
}
}
private void parseClassSignature() {
SignatureParser sp = SignatureParser.fromNode(this);
if (sp == null) {
return;
}
try {
// parse class generic map
genericMap = sp.consumeGenericMap();
// parse super class signature
superClass = ClassInfo.fromType(sp.consumeType());
// parse interfaces signatures
for (int i = 0; i < interfaces.size(); i++) {
ArgType type = sp.consumeType();
if (type != null) {
interfaces.set(i, ClassInfo.fromType(type));
} else {
break;
}
}
} catch (JadxRuntimeException e) {
LOG.error("Class signature parse error: " + this, e);
}
}
private void setFieldsTypesFromSignature() {
for (FieldNode field : fields) {
SignatureParser sp = SignatureParser.fromNode(field);
if (sp != null) {
try {
ArgType gType = sp.consumeType();
if (gType != null) {
field.setType(gType);
}
} catch (JadxRuntimeException e) {
LOG.error("Field signature parse error: " + field, e);
}
}
}
}
@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) {
return searchFieldByName(field.getName());
}
public FieldNode searchFieldByName(String name) {
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 ClassNode getParentClass() {
if (parentClass == null) {
if (clsInfo.isInner()) {
ClassNode parent = dex().resolveClass(clsInfo.getParentClass());
parent = parent == null ? this : parent;
parentClass = parent;
} else {
parentClass = this;
}
}
return parentClass;
}
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.info.MethodInfo;
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.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,6 +33,8 @@ public class DexNode {
private final List<ClassNode> classes = new ArrayList<ClassNode>();
private final String[] strings;
private final Map<Object, FieldNode> constFields = new HashMap<Object, FieldNode>();
public DexNode(RootNode root, InputFile input) {
this.root = root;
this.dexBuf = input.getDexBuffer();
@@ -59,6 +65,18 @@ public class DexNode {
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) {
@@ -91,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) {
@@ -110,5 +128,4 @@ public class DexNode {
public String toString() {
return "DEX";
}
}
@@ -0,0 +1,42 @@
package jadx.core.dex.nodes;
public class Edge {
private final BlockNode source;
private final BlockNode target;
public Edge(BlockNode source, BlockNode target) {
this.source = source;
this.target = target;
}
public BlockNode getSource() {
return source;
}
public BlockNode getTarget() {
return target;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Edge edge = (Edge) o;
return source.equals(edge.source) && target.equals(edge.target);
}
@Override
public int hashCode() {
return source.hashCode() + 31 * target.hashCode();
}
@Override
public String toString() {
return "Edge: " + source + " -> " + target;
}
}
@@ -0,0 +1,71 @@
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 ClassNode parent;
private final FieldInfo fieldInfo;
private final AccessInfo accFlags;
private ArgType type; // store signature
public FieldNode(ClassNode cls, Field field) {
this.parent = cls;
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;
}
public ClassNode getParentClass() {
return parent;
}
@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,12 @@
package jadx.core.dex.nodes;
import java.util.List;
public interface IRegion extends IContainer {
IRegion getParent();
List<IContainer> getSubBlocks();
boolean replaceSubBlock(IContainer oldBlock, IContainer newBlock);
}
@@ -1,14 +1,14 @@
package jadx.dex.nodes;
package jadx.core.dex.nodes;
import jadx.dex.attributes.AttrNode;
import jadx.core.dex.attributes.AttrNode;
import java.util.List;
public class InsnContainer extends AttrNode implements IBlock {
private List<InsnNode> insns;
private final 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,23 +24,21 @@ public class InsnNode extends AttrNode {
protected int offset;
protected int insnHashCode = super.hashCode();
protected InsnNode(InsnType type) {
this(type, 1);
}
public InsnNode(InsnType type, int argsCount) {
this.insnType = type;
this.offset = -1;
if (argsCount == 0)
if (argsCount == 0) {
this.arguments = Collections.emptyList();
else
} else {
this.arguments = new ArrayList<InsnArg>(argsCount);
}
}
public void setResult(RegisterArg res) {
if (res != null)
if (res != null) {
res.setParentInsn(this);
}
this.result = res;
}
@@ -71,8 +69,9 @@ public class InsnNode extends AttrNode {
public boolean containsArg(RegisterArg arg) {
for (InsnArg a : arguments) {
if (a == arg || (a.isRegister() && a.getRegNum() == arg.getRegNum()))
if (a == arg || (a.isRegister() && ((RegisterArg) a).getRegNum() == arg.getRegNum())) {
return true;
}
}
return false;
}
@@ -88,12 +87,11 @@ public class InsnNode extends AttrNode {
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;
}
if (arg.isInsnWrap() && ((InsnWrapArg) arg).getWrapInsn().replaceArg(from, to)) {
return true;
}
}
return false;
@@ -125,10 +123,37 @@ 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;
default:
return false;
}
}
@@ -151,14 +176,25 @@ public class InsnNode extends AttrNode {
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (hashCode() != obj.hashCode()) return false;
if (!(obj instanceof InsnNode)) return false;
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (hashCode() != obj.hashCode()) {
return false;
}
if (!(obj instanceof InsnNode)) {
return false;
}
InsnNode other = (InsnNode) obj;
if (insnType != other.insnType) return false;
if (arguments.size() != other.arguments.size()) return false;
if (insnType != other.insnType) {
return false;
}
if (arguments.size() != other.arguments.size()) {
return false;
}
// TODO !!! finish equals
return true;

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