Compare commits

...

90 Commits

Author SHA1 Message Date
Skylot 6bac5c162e core: select correct array type element 2013-12-25 23:55:45 +04:00
Skylot 5cbf71bde6 core: remove unnecessary return instructions for void methods 2013-12-25 23:49:40 +04:00
Skylot a85d382e89 core: reformat TryCatchBlock class 2013-12-24 22:38:34 +04:00
Skylot 4caa58f5fd core: use correct argument wrap method 2013-12-24 22:23:12 +04:00
Skylot 43913d47ec core: fix method definition 2013-12-24 22:20:48 +04:00
Skylot 9f51cabf69 core: fix anonymous class codegen 2013-12-20 23:22:27 +04:00
Skylot 1c60e5e315 core: inline anonymous classes 2013-12-13 17:50:41 +04:00
Skylot a9290f3131 core: remove synthetic constructors 2013-12-13 17:50:41 +04:00
Skylot e46dfc555e core: redone return blocks splitting for fix issue #4 2013-12-13 17:50:33 +04:00
Skylot e54b764588 fix code style issues reported by sonar 2013-12-07 16:11:12 +04:00
Skylot 37f03bcf9e add drone.io for download artifacts 2013-12-07 00:16:45 +04:00
Skylot 1d0f23dfbb update gradle to 1.9 2013-12-07 00:15:10 +04:00
Skylot 30355cc9d6 core: remove synthetic fields for inner classes 2013-12-06 23:42:04 +04:00
Skylot ed67f8e118 core: make strict shrink code implementation 2013-12-06 16:37:30 +04:00
Skylot 4531256005 add sonar code checking 2013-12-06 16:27:34 +04:00
Skylot 662ebb6451 core: don't add super call without args 2013-11-29 15:35:04 +04:00
Skylot 4a63f52259 gui: fix ui tabs handling 2013-11-27 23:00:15 +04:00
Skylot c416f77e99 core: fix android specific class handler 2013-11-27 22:14:34 +04:00
skylot fde431d131 Merge pull request #3 from 13-beta2/master
Core fix
2013-11-27 10:04:22 -08:00
13.beta2 272e0d3754 core: fix missing code after 'if' inside loop 2013-11-26 23:56:17 +04:00
Skylot b44a1e3a4f gui: fix tab selection 2013-11-26 22:14:49 +04:00
Skylot b18dabee15 gui: adjust tabbed ui appearance 2013-11-24 17:54:05 +04:00
Skylot b6befbdcf2 gui: initial tabbed ui implementation 2013-11-21 00:15:13 +04:00
Skylot 2cfc208aa9 core: fix constant fields values retrieval 2013-11-18 21:06:45 +04:00
13.beta2 132b8d0618 core: inlining invoke arguments
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
setContentView(2130837505);
-->
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
setContentView(R.layout.samplelayout);
2013-11-16 17:01:46 +04:00
skylot 5dc4c28da5 Merge pull request #2 from 13-beta2/master
Handy core improvement
2013-11-15 11:00:01 -08:00
13.beta2 7342ae18a6 core: fix to 4f61ddd 2013-11-15 22:31:44 +04:00
13.beta2 eafe080c41 core: omit redundant brackets in case blocks 2013-11-14 01:36:46 +04:00
13.beta2 4f61ddd4b7 core: inlining return results
int r0i;
switch(arg0) {
case 1:
r0i = 255;
return r0i;
}
r0i = 128;
return r0i;
-->
switch(arg0) {
case 1:
return 255;
}
return 128;
2013-11-14 01:36:44 +04:00
13.beta2 86b0458673 core: replace switch labels with matched static final fields, searching up to root ClassNode 2013-11-14 01:36:42 +04:00
Skylot 36cfc9d189 core tests: add inner classes in internal tests 2013-11-12 23:24:21 +04:00
Skylot b2f189b572 core: process complex condition in loop header 2013-11-12 21:00:05 +04:00
Skylot eec524ad85 core: make methods arguments types immutable 2013-11-10 14:15:29 +04:00
Skylot d94087b939 core: fix encoded value parser for signed and floating point numbers 2013-10-23 23:27:53 +04:00
Skylot 1ba19d3600 core: fix annotations number decoder 2013-10-22 22:37:53 +04:00
Skylot 07402ba4c0 core: fix "null" enum field 2013-10-19 18:54:03 +04:00
Skylot d60698206e core: fix type in fill-array instruction 2013-09-29 19:36:56 +04:00
Skylot c59b65e71c build: add 'dist' task 2013-09-28 15:51:45 +04:00
Skylot bd4c61d300 core: fix incorrect float values processing 2013-09-28 15:17:20 +04:00
Skylot 00a6b6efd2 core: add tests options, change log format 2013-09-28 13:38:14 +04:00
Skylot 04ac3b2eb7 core: fix classes import naming 2013-09-26 22:28:27 +04:00
Skylot 6bc2d3321c code refactoring 2013-09-25 18:07:14 +04:00
Skylot c95211925e core: omit 'this' for methods and fields 2013-09-24 23:01:01 +04:00
Skylot 95e9da36c5 core: simplify conditions, omit redundant parenthesis 2013-09-24 22:58:32 +04:00
Skylot 9bf7270bf3 reformat code, resolve compiler warnings 2013-09-24 21:59:32 +04:00
Skylot a99e0e9618 upgrade to gradle 1.8 2013-09-24 21:46:43 +04:00
Skylot 01c4706013 core: improve chained conditions processing 2013-09-23 23:19:27 +04:00
Skylot 89c7b9a848 core: fix ArgType.equals 2013-09-21 22:23:50 +04:00
Skylot 1358a05a74 core: omit redundant brackets in conditions 2013-09-21 19:32:10 +04:00
Skylot 1b0a8990f7 core: move tests 2013-09-21 19:14:51 +04:00
Skylot 4edfffae27 core: remove not needed casts 2013-09-14 17:38:38 +04:00
Skylot cde8d72510 core: don't add redundant brackets 2013-09-12 23:32:47 +04:00
Skylot d7ce0245f6 core: convert arithmetic operations on field to arith instruction 2013-09-09 23:30:10 +04:00
Skylot 49c5ceb06e core: add framework for internal tests 2013-09-09 23:21:47 +04:00
Skylot 4c03a4245b improve jadx api 2013-09-08 16:51:17 +04:00
Skylot 4454e013c4 gui: add internal tests 2013-09-04 23:23:16 +04:00
Skylot 1e7546f4a3 update tests 2013-09-04 23:23:16 +04:00
Skylot 7742d34111 decrease memory requirements 2013-09-04 23:23:16 +04:00
Skylot a413aaf140 update gradle to 1.7 2013-09-04 23:23:07 +04:00
Skylot e94396532e gui: open file selection dialog on start 2013-08-11 00:05:09 +04:00
Skylot cc1be673e7 core: sort classes in package and methods 2013-08-11 00:03:59 +04:00
Skylot f9e87d4da0 gui: set bigger window size at start 2013-08-10 23:34:49 +04:00
Skylot ab8fa23fc3 cli: move specific code from common jadx args 2013-08-10 22:52:06 +04:00
Skylot 7985466213 add Travis CI integration 2013-08-06 22:24:02 +04:00
Skylot e92ed48502 samples: remove generated code from gradle source set 2013-08-03 15:56:33 +04:00
Skylot c508e72c19 core: fixed types for arguments from overloaded methods 2013-08-02 14:00:55 +04:00
Skylot 940de24099 core: split const-string and const-class instructions 2013-08-02 13:59:49 +04:00
Skylot 6ddb71e21f core: add classpath for precise class types resolving 2013-08-01 18:19:54 +04:00
Skylot d0f120c314 core: fix string concatenation 2013-07-31 13:31:41 +04:00
Skylot 54f4c6d2cb build samples without debug info, fix try/catch processing 2013-07-29 18:44:01 +04:00
Skylot 1f21760bbe rename jadx-cli, update build scripts 2013-07-27 15:43:07 +04:00
Skylot 67eb55a95d gui: add type and access info to classes tree 2013-07-26 23:04:42 +04:00
Skylot fa097cc6b2 gui: add action for save all decompiled source 2013-07-26 15:57:29 +04:00
Skylot 34222dae0a gui: add search bar 2013-07-25 23:39:47 +04:00
Skylot 3a62d04376 gui: fix tree class switch 2013-07-24 17:43:32 +04:00
Skylot ca2c935f65 gui: don't create output directory on startup 2013-07-24 17:40:55 +04:00
Skylot ddf2174cae core: fix duplicated imports 2013-07-24 17:40:55 +04:00
Skylot 7096c38299 fix gradle scripts, update readme 2013-07-24 17:40:49 +04:00
Skylot c4cdd8514d gui: add fields and methods to tree 2013-07-23 23:01:48 +04:00
Skylot 25b2c8fe5b core: store line info, add fields and methods to api, refactoring 2013-07-23 22:59:00 +04:00
Skylot 36da79feb8 gui: add icons for packages tree, add hierarchical mode 2013-07-22 22:43:00 +04:00
Skylot 571b5590ac gui: add icons to classes tree 2013-07-21 19:03:13 +04:00
Skylot 7eb5defc2a Update gradle build files 2013-07-10 23:47:45 +04:00
Skylot ce7d6f0156 Add jadx-gui, restructure src directory 2013-07-10 22:57:39 +04:00
Skylot cbbb73355b Remove unneeded 'return' instructions 2013-07-04 23:47:14 +04:00
Skylot f51d633707 Inline all literal constants (not only boolean) 2013-07-03 23:44:17 +04:00
Skylot bca90c1f41 Don't add 'public' for annotations methods 2013-07-03 23:44:17 +04:00
skylot 17c0fd21d2 Publish release on github 2013-07-03 00:03:35 +04:00
Skylot fb43d716d9 Replace constants with matched static final fields 2013-07-01 23:49:19 +04:00
Skylot 3598a1279c Process complex 'if' conditions, refactoring 2013-06-30 22:48:16 +04:00
291 changed files with 9265 additions and 3131 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
```
+52 -76
View File
@@ -1,94 +1,70 @@
apply plugin: 'java'
apply plugin: 'application'
ext.jadxVersion = file('version').readLines().get(0)
apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: 'sonar-runner'
sourceCompatibility = 1.6
targetCompatibility = 1.6
subprojects {
apply plugin: 'java'
apply plugin: 'idea'
apply plugin: 'eclipse'
version = file('version').readLines().get(0)
sourceCompatibility = 1.6
targetCompatibility = 1.6
mainClassName = "jadx.Main"
manifest.mainAttributes("jadx-version" : version)
version = jadxVersion
project.ext {
mainSamplesClass = "jadx.samples.RunTests"
samplesJadxSrcDir = "${buildDir}/samples-jadx/src"
}
dependencies {
compile 'com.google.android.tools:dx:1.7'
compile 'com.beust:jcommander:1.30'
compile 'org.slf4j:slf4j-api:1.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.5'
testCompile 'junit:junit:4.11'
testCompile "org.mockito:mockito-core:1.9.5"
}
task samplesJadxCreate(type: JavaExec, dependsOn: [compileJava, samplesJar]) {
classpath = sourceSets.main.output + configurations.compile
main = mainClassName
args = ['-d', samplesJadxSrcDir, samplesJar.archivePath]
}
compileSamplesJadxJava.dependsOn samplesJadxCreate
task samplesJadxRun(type: JavaExec, dependsOn: compileSamplesJadxJava) {
classpath = sourceSets.samplesJadx.output
main = mainSamplesClass
}
task samples (dependsOn: samplesJadxRun) {
}
//check.dependsOn samples
build.dependsOn distZip
build.dependsOn installApp
startScripts {
doLast {
// increase default max heap size
String var = 'DEFAULT_JVM_OPTS='
String args = '-Xmx1400M'
unixScript.text = unixScript.text.replace(var + '""', var + '"' + args + '"')
windowsScript.text = windowsScript.text.replace(var, var + args)
repositories {
mavenCentral()
}
}
applicationDistribution.with {
into('') {
from '.'
include 'README.md'
include 'NOTICE'
task copyArtifacts(type: Sync, dependsOn: ['jadx-cli:installApp', 'jadx-gui:installApp']) {
destinationDir file("$buildDir/jadx")
['jadx-cli', 'jadx-gui'].each {
from tasks.getByPath(":${it}:installApp").destinationDir
}
}
task pack(type: Zip, dependsOn: copyArtifacts) {
destinationDir buildDir
archiveName "jadx-${jadxVersion}.zip"
from copyArtifacts.destinationDir
}
task dist(dependsOn: pack) {
description = 'Build jadx distribution zip'
}
task samples(dependsOn: 'jadx-samples:samples') {
}
task build(dependsOn: [dist, samples]) {
}
task clean(type: Delete) {
delete buildDir
}
task wrapper(type: Wrapper) {
gradleVersion = '1.6'
gradleVersion = '1.9'
}
Binary file not shown.
+1 -1
View File
@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=http\://services.gradle.org/distributions/gradle-1.6-bin.zip
distributionUrl=http\://services.gradle.org/distributions/gradle-1.9-bin.zip
+28
View File
@@ -0,0 +1,28 @@
apply plugin: 'application'
mainClassName = 'jadx.cli.JadxCLI'
applicationName = 'jadx'
dependencies {
compile(project(':jadx-core'))
compile 'com.beust:jcommander:1.30'
}
startScripts {
doLast {
// increase default max heap size
String var = 'DEFAULT_JVM_OPTS='
String args = '-Xmx1300M'
unixScript.text = unixScript.text.replace(var + '""', var + '"' + args + '"')
windowsScript.text = windowsScript.text.replace(var, var + args)
}
}
applicationDistribution.with {
into('') {
from '../.'
include 'README.md'
include 'NOTICE'
}
}
@@ -0,0 +1,68 @@
package jadx.cli;
import jadx.api.Decompiler;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.exceptions.JadxException;
import java.io.File;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class JadxCLI {
private static final Logger LOG = LoggerFactory.getLogger(JadxCLI.class);
public static void main(String[] args) {
try {
JadxCLIArgs jadxArgs = new JadxCLIArgs(args);
checkArgs(jadxArgs);
processAndSave(jadxArgs);
} catch (Exception e) {
LOG.error(e.getMessage());
System.exit(1);
}
}
private static void processAndSave(JadxCLIArgs jadxArgs) {
try {
Decompiler jadx = new Decompiler(jadxArgs);
jadx.loadFiles(jadxArgs.getInput());
jadx.setOutputDir(jadxArgs.getOutDir());
jadx.save();
LOG.info("done");
} catch (Throwable e) {
LOG.error("jadx error:", e);
}
int errorsCount = ErrorsCounter.getErrorCount();
if (errorsCount != 0) {
ErrorsCounter.printReport();
}
System.exit(errorsCount);
}
private static void checkArgs(JadxCLIArgs jadxArgs) throws JadxException {
if (jadxArgs.getInput().isEmpty()) {
LOG.error("Please specify input file");
jadxArgs.printUsage();
System.exit(1);
}
File outputDir = jadxArgs.getOutDir();
if (outputDir == null) {
String outDirName;
File file = jadxArgs.getInput().get(0);
String name = file.getName();
int pos = name.lastIndexOf('.');
if (pos != -1) {
outDirName = name.substring(0, pos);
} else {
outDirName = name + "-jadx-out";
}
LOG.info("output directory: " + outDirName);
outputDir = new File(outDirName);
jadxArgs.setOutputDir(outputDir);
}
if (outputDir.exists() && !outputDir.isDirectory()) {
throw new JadxException("Output directory exists as file " + outputDir);
}
}
}
@@ -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;
public void processArgs() {
if (isPrintHelp()) {
printUsage();
System.exit(0);
}
try {
if (threadsCount <= 0)
throw new JadxException("Threads count must be positive");
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);
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());
@@ -123,13 +121,12 @@ public class JadxArgs implements IJadxArgs {
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;
}
@@ -144,11 +141,22 @@ public class JadxArgs implements IJadxArgs {
str.append(' ');
}
@Override
public List<File> getInput() {
return input;
}
public File getOutDir() {
return outputDir;
}
public void setOutputDir(File outputDir) {
this.outputDir = outputDir;
}
public boolean isPrintHelp() {
return printHelp;
}
@Override
public int getThreadsCount() {
return threadsCount;
@@ -164,11 +172,6 @@ public class JadxArgs implements IJadxArgs {
return rawCfgOutput;
}
@Override
public List<InputFile> getInput() {
return input;
}
@Override
public boolean isFallbackMode() {
return fallbackMode;
@@ -178,10 +181,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>
+9
View File
@@ -0,0 +1,9 @@
ext.jadxClasspath = 'clsp-data/android-4.3.jar'
dependencies {
compile 'com.google.android.tools:dx:1.7'
compile 'ch.qos.logback:logback-classic:1.0.13'
runtime files(jadxClasspath)
}
Binary file not shown.
@@ -0,0 +1,198 @@
package jadx.api;
import jadx.core.Jadx;
import jadx.core.ProcessClass;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.visitors.IDexTreeVisitor;
import jadx.core.dex.visitors.SaveCode;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.exceptions.CodegenException;
import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.utils.files.InputFile;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Jadx API usage example:
* <pre><code>
* Decompiler jadx = new Decompiler();
* jadx.loadFile(new File("classes.dex"));
* jadx.setOutputDir(new File("out"));
* jadx.save();
* </code></pre>
* <p/>
* Instead of 'save()' you can get list of decompiled classes:
* <pre><code>
* for(JavaClass cls : jadx.getClasses()) {
* System.out.println(cls.getCode());
* }
* </code></pre>
*/
public final class Decompiler {
private static final Logger LOG = LoggerFactory.getLogger(Decompiler.class);
private final IJadxArgs args;
private final List<InputFile> inputFiles = new ArrayList<InputFile>();
private File outDir;
private RootNode root;
private List<IDexTreeVisitor> passes;
public Decompiler() {
this.args = new DefaultJadxArgs();
init();
}
public Decompiler(IJadxArgs jadxArgs) {
this.args = jadxArgs;
init();
}
public void setOutputDir(File outDir) {
this.outDir = outDir;
init();
}
void init() {
if (outDir == null) {
outDir = new File("jadx-output");
}
this.passes = Jadx.getPassesList(args, outDir);
}
public void loadFile(File file) throws IOException, DecodeException {
loadFiles(Arrays.asList(file));
}
public void loadFiles(List<File> files) throws IOException, DecodeException {
if (files.isEmpty()) {
throw new JadxRuntimeException("Empty file list");
}
inputFiles.clear();
for (File file : files) {
inputFiles.add(new InputFile(file));
}
parse();
}
public void save() {
try {
ExecutorService ex = getSaveExecutor();
ex.awaitTermination(1, TimeUnit.DAYS);
} catch (InterruptedException e) {
LOG.error("Save interrupted", e);
}
}
public ThreadPoolExecutor getSaveExecutor() {
if (root == null) {
throw new JadxRuntimeException("No loaded files");
}
int threadsCount = args.getThreadsCount();
LOG.debug("processing threads count: {}", threadsCount);
ArrayList<IDexTreeVisitor> passList = new ArrayList<IDexTreeVisitor>(passes);
SaveCode savePass = new SaveCode(outDir, args);
passList.add(savePass);
LOG.info("processing ...");
ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(threadsCount);
for (ClassNode cls : root.getClasses(false)) {
if (cls.getCode() == null) {
ProcessClass job = new ProcessClass(cls, passList);
executor.execute(job);
} else {
try {
savePass.visit(cls);
} catch (CodegenException e) {
LOG.error("Can't save class {}", cls, e);
}
}
}
executor.shutdown();
return executor;
}
public List<JavaClass> getClasses() {
List<ClassNode> classNodeList = root.getClasses(false);
List<JavaClass> classes = new ArrayList<JavaClass>(classNodeList.size());
for (ClassNode classNode : classNodeList) {
classes.add(new JavaClass(this, classNode));
}
return Collections.unmodifiableList(classes);
}
public List<JavaPackage> getPackages() {
List<JavaClass> classes = getClasses();
Map<String, List<JavaClass>> map = new HashMap<String, List<JavaClass>>();
for (JavaClass javaClass : classes) {
String pkg = javaClass.getPackage();
List<JavaClass> clsList = map.get(pkg);
if (clsList == null) {
clsList = new ArrayList<JavaClass>();
map.put(pkg, clsList);
}
clsList.add(javaClass);
}
List<JavaPackage> packages = new ArrayList<JavaPackage>(map.size());
for (Map.Entry<String, List<JavaClass>> entry : map.entrySet()) {
packages.add(new JavaPackage(entry.getKey(), entry.getValue()));
}
Collections.sort(packages);
for (JavaPackage pkg : packages) {
Collections.sort(pkg.getClasses(), new Comparator<JavaClass>() {
@Override
public int compare(JavaClass o1, JavaClass o2) {
return o1.getShortName().compareTo(o2.getShortName());
}
});
}
return Collections.unmodifiableList(packages);
}
public int getErrorsCount() {
return ErrorsCounter.getErrorCount();
}
void parse() throws DecodeException {
ClassInfo.clearCache();
ErrorsCounter.reset();
root = new RootNode();
LOG.info("loading ...");
root.load(inputFiles);
}
void processClass(ClassNode cls) {
try {
ProcessClass job = new ProcessClass(cls, passes);
LOG.info("processing class {} ...", cls);
job.run();
} catch (Throwable e) {
LOG.error("Process class error", e);
}
}
RootNode getRoot() {
return root;
}
}
@@ -0,0 +1,29 @@
package jadx.api;
public class DefaultJadxArgs implements IJadxArgs {
@Override
public int getThreadsCount() {
return Runtime.getRuntime().availableProcessors();
}
@Override
public boolean isCFGOutput() {
return false;
}
@Override
public boolean isRawCFGOutput() {
return false;
}
@Override
public boolean isFallbackMode() {
return false;
}
@Override
public boolean isVerbose() {
return false;
}
}
@@ -0,0 +1,13 @@
package jadx.api;
public interface IJadxArgs {
int getThreadsCount();
boolean isCFGOutput();
boolean isRawCFGOutput();
boolean isFallbackMode();
boolean isVerbose();
}
@@ -0,0 +1,113 @@
package jadx.api;
import jadx.core.codegen.CodeWriter;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.MethodNode;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public final class JavaClass {
private final Decompiler decompiler;
private final ClassNode cls;
private final List<JavaClass> innerClasses;
private final List<JavaField> fields;
private final List<JavaMethod> methods;
JavaClass(Decompiler decompiler, ClassNode classNode) {
this.decompiler = decompiler;
this.cls = classNode;
int inClsCount = cls.getInnerClasses().size();
if (inClsCount == 0) {
this.innerClasses = Collections.emptyList();
} else {
List<JavaClass> list = new ArrayList<JavaClass>(inClsCount);
for (ClassNode inner : cls.getInnerClasses()) {
list.add(new JavaClass(decompiler, inner));
}
this.innerClasses = Collections.unmodifiableList(list);
}
int fieldsCount = cls.getFields().size();
if (fieldsCount == 0) {
this.fields = Collections.emptyList();
} else {
List<JavaField> flds = new ArrayList<JavaField>(fieldsCount);
for (FieldNode f : cls.getFields()) {
flds.add(new JavaField(f));
}
this.fields = Collections.unmodifiableList(flds);
}
int methodsCount = cls.getMethods().size();
if (methodsCount == 0) {
this.methods = Collections.emptyList();
} else {
List<JavaMethod> mths = new ArrayList<JavaMethod>(methodsCount);
for (MethodNode m : cls.getMethods()) {
if (!m.getAccessFlags().isSynthetic()) {
mths.add(new JavaMethod(m));
}
}
Collections.sort(mths, new Comparator<JavaMethod>() {
@Override
public int compare(JavaMethod o1, JavaMethod o2) {
return o1.getName().compareTo(o2.getName());
}
});
this.methods = Collections.unmodifiableList(mths);
}
}
public String getCode() {
CodeWriter code = cls.getCode();
if (code == null) {
decompiler.processClass(cls);
code = cls.getCode();
}
return code != null ? code.toString() : "error processing class";
}
public String getFullName() {
return cls.getFullName();
}
public String getShortName() {
return cls.getShortName();
}
public String getPackage() {
return cls.getPackage();
}
public AccessInfo getAccessInfo() {
return cls.getAccessFlags();
}
public List<JavaClass> getInnerClasses() {
return innerClasses;
}
public List<JavaField> getFields() {
return fields;
}
public List<JavaMethod> getMethods() {
return methods;
}
@Override
public String toString() {
return getFullName();
}
public int getDecompiledLine() {
return cls.getDecompiledLine();
}
}
@@ -0,0 +1,30 @@
package jadx.api;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.FieldNode;
public final class JavaField {
private final FieldNode field;
public JavaField(FieldNode f) {
this.field = f;
}
public String getName() {
return field.getName();
}
public AccessInfo getAccessFlags() {
return field.getAccessFlags();
}
public ArgType getType() {
return field.getType();
}
public int getDecompiledLine() {
return field.getDecompiledLine();
}
}
@@ -0,0 +1,50 @@
package jadx.api;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.MethodNode;
import java.util.List;
public final class JavaMethod {
private final MethodNode mth;
public JavaMethod(MethodNode m) {
this.mth = m;
}
public String getName() {
MethodInfo mi = mth.getMethodInfo();
if (mi.isConstructor()) {
return mth.getParentClass().getShortName();
} else if (mi.isClassInit()) {
return "static";
}
return mi.getName();
}
public AccessInfo getAccessFlags() {
return mth.getAccessFlags();
}
public List<ArgType> getArguments() {
return mth.getMethodInfo().getArgumentsTypes();
}
public ArgType getReturnType() {
return mth.getReturnType();
}
public boolean isConstructor() {
return mth.getMethodInfo().isConstructor();
}
public boolean isClassInit() {
return mth.getMethodInfo().isClassInit();
}
public int getDecompiledLine() {
return mth.getDecompiledLine();
}
}
@@ -0,0 +1,45 @@
package jadx.api;
import java.util.List;
public final class JavaPackage implements Comparable<JavaPackage> {
private final String name;
private final List<JavaClass> classes;
JavaPackage(String name, List<JavaClass> classes) {
this.name = name;
this.classes = classes;
}
public String getName() {
return name;
}
public List<JavaClass> getClasses() {
return classes;
}
@Override
public int compareTo(JavaPackage o) {
return name.compareTo(o.name);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
JavaPackage that = (JavaPackage) o;
if (!name.equals(that.name)) return false;
return true;
}
@Override
public int hashCode() {
return name.hashCode();
}
@Override
public String toString() {
return name;
}
}
@@ -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,5 @@ 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_";
}
@@ -0,0 +1,99 @@
package jadx.core;
import jadx.api.IJadxArgs;
import jadx.core.codegen.CodeGen;
import jadx.core.dex.visitors.BlockMakerVisitor;
import jadx.core.dex.visitors.ClassModifier;
import jadx.core.dex.visitors.CodeShrinker;
import jadx.core.dex.visitors.ConstInlinerVisitor;
import jadx.core.dex.visitors.DotGraphVisitor;
import jadx.core.dex.visitors.EnumVisitor;
import jadx.core.dex.visitors.FallbackModeVisitor;
import jadx.core.dex.visitors.IDexTreeVisitor;
import jadx.core.dex.visitors.MethodInlinerVisitor;
import jadx.core.dex.visitors.ModVisitor;
import jadx.core.dex.visitors.SimplifyVisitor;
import jadx.core.dex.visitors.regions.CheckRegions;
import jadx.core.dex.visitors.regions.CleanRegions;
import jadx.core.dex.visitors.regions.PostRegionVisitor;
import jadx.core.dex.visitors.regions.ProcessVariables;
import jadx.core.dex.visitors.regions.RegionMakerVisitor;
import jadx.core.dex.visitors.typeresolver.FinishTypeResolver;
import jadx.core.dex.visitors.typeresolver.TypeResolver;
import jadx.core.utils.Utils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.Manifest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Jadx {
private static final Logger LOG = LoggerFactory.getLogger(Jadx.class);
static {
if (Consts.DEBUG)
LOG.info("debug enabled");
if (Jadx.class.desiredAssertionStatus())
LOG.info("assertions enabled");
}
public static List<IDexTreeVisitor> getPassesList(IJadxArgs args, File outDir) {
List<IDexTreeVisitor> passes = new ArrayList<IDexTreeVisitor>();
if (args.isFallbackMode()) {
passes.add(new FallbackModeVisitor());
} else {
passes.add(new BlockMakerVisitor());
passes.add(new TypeResolver());
if (args.isRawCFGOutput())
passes.add(new DotGraphVisitor(outDir, false, true));
passes.add(new ConstInlinerVisitor());
passes.add(new FinishTypeResolver());
passes.add(new ModVisitor());
passes.add(new EnumVisitor());
if (args.isCFGOutput())
passes.add(new DotGraphVisitor(outDir, false));
passes.add(new RegionMakerVisitor());
passes.add(new PostRegionVisitor());
passes.add(new CodeShrinker());
passes.add(new SimplifyVisitor());
passes.add(new ProcessVariables());
passes.add(new CheckRegions());
if (args.isCFGOutput())
passes.add(new DotGraphVisitor(outDir, true));
passes.add(new MethodInlinerVisitor());
passes.add(new ClassModifier());
passes.add(new CleanRegions());
}
passes.add(new CodeGen(args));
return passes;
}
public static String getVersion() {
try {
Enumeration<URL> resources = Utils.class.getClassLoader().getResources("META-INF/MANIFEST.MF");
while (resources.hasMoreElements()) {
Manifest manifest = new Manifest(resources.nextElement().openStream());
String ver = manifest.getMainAttributes().getValue("jadx-version");
if (ver != null)
return ver;
}
} catch (IOException e) {
LOG.error("Can't get manifest file", e);
}
return "dev";
}
}
@@ -1,22 +1,22 @@
package jadx;
package jadx.core;
import jadx.dex.nodes.ClassNode;
import jadx.dex.visitors.DepthTraverser;
import jadx.dex.visitors.IDexTreeVisitor;
import jadx.utils.exceptions.DecodeException;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.visitors.DepthTraverser;
import jadx.core.dex.visitors.IDexTreeVisitor;
import jadx.core.utils.exceptions.DecodeException;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
class ProcessClass implements Runnable {
public final class ProcessClass implements Runnable {
private static final Logger LOG = LoggerFactory.getLogger(ProcessClass.class);
private final ClassNode cls;
private final List<IDexTreeVisitor> passes;
ProcessClass(ClassNode cls, List<IDexTreeVisitor> passes) {
public ProcessClass(ClassNode cls, List<IDexTreeVisitor> passes) {
this.cls = cls;
this.passes = passes;
}
@@ -0,0 +1,229 @@
package jadx.core.clsp;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.exceptions.JadxRuntimeException;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Classes list for import into classpath graph
*/
public class ClsSet {
private static final Logger LOG = LoggerFactory.getLogger(ClsSet.class);
private static final String CLST_EXTENSION = ".jcst";
private static final String CLST_FILENAME = "core" + CLST_EXTENSION;
private static final String CLST_PKG_PATH = ClsSet.class.getPackage().getName().replace('.', '/');
private static final String JADX_CLS_SET_HEADER = "jadx-cst";
private static final int VERSION = 1;
private static final String STRING_CHARSET = "US-ASCII";
private NClass[] classes;
public void load(RootNode root) {
List<ClassNode> list = root.getClasses(true);
Map<String, NClass> names = new HashMap<String, NClass>(list.size());
int k = 0;
for (ClassNode cls : list) {
String clsRawName = cls.getRawName();
if (cls.getAccessFlags().isPublic()) {
NClass nClass = new NClass(clsRawName, k);
if (names.put(clsRawName, nClass) != null) {
throw new JadxRuntimeException("Duplicate class: " + clsRawName);
}
k++;
} else {
names.put(clsRawName, null);
}
}
classes = new NClass[k];
k = 0;
for (ClassNode cls : list) {
if (cls.getAccessFlags().isPublic()) {
NClass nClass = getCls(cls.getRawName(), names);
nClass.setParents(makeParentsArray(cls, names));
classes[k] = nClass;
k++;
}
}
}
public static NClass[] makeParentsArray(ClassNode cls, Map<String, NClass> names) {
List<NClass> parents = new ArrayList<NClass>(1 + cls.getInterfaces().size());
ClassInfo superClass = cls.getSuperClass();
if (superClass != null) {
NClass c = getCls(superClass.getRawName(), names);
if (c != null) {
parents.add(c);
}
}
for (ClassInfo iface : cls.getInterfaces()) {
NClass c = getCls(iface.getRawName(), names);
if (c != null) {
parents.add(c);
}
}
return parents.toArray(new NClass[parents.size()]);
}
private static NClass getCls(String fullName, Map<String, NClass> names) {
NClass id = names.get(fullName);
if (id == null && !names.containsKey(fullName)) {
LOG.warn("Class not found: " + fullName);
}
return id;
}
void save(File output) throws IOException {
Utils.makeDirsForFile(output);
BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(output));
String outputName = output.getName();
if (outputName.endsWith(CLST_EXTENSION)) {
save(outputStream);
} else if (outputName.endsWith(".jar")) {
ZipOutputStream out = new ZipOutputStream(outputStream);
try {
out.putNextEntry(new ZipEntry(CLST_PKG_PATH + "/" + CLST_FILENAME));
save(out);
out.closeEntry();
} finally {
out.close();
outputStream.close();
}
} else {
throw new JadxRuntimeException("Unknown file format: " + outputName);
}
}
public void save(OutputStream output) throws IOException {
DataOutputStream out = new DataOutputStream(output);
out.writeBytes(JADX_CLS_SET_HEADER);
out.writeByte(VERSION);
LOG.info("Classes count: " + classes.length);
out.writeInt(classes.length);
for (NClass cls : classes) {
writeString(out, cls.getName());
}
for (NClass cls : classes) {
NClass[] parents = cls.getParents();
out.writeByte(parents.length);
for (NClass parent : parents) {
out.writeInt(parent.getId());
}
}
}
public void load() throws IOException, DecodeException {
InputStream input = getClass().getResourceAsStream(CLST_FILENAME);
if (input == null) {
throw new JadxRuntimeException("Can't load classpath file: " + CLST_FILENAME);
}
load(input);
}
public void load(File input) throws IOException, DecodeException {
String name = input.getName();
InputStream inputStream = new FileInputStream(input);
if (name.endsWith(CLST_EXTENSION)) {
load(inputStream);
} else if (name.endsWith(".jar")) {
ZipInputStream in = new ZipInputStream(inputStream);
try {
ZipEntry entry = in.getNextEntry();
while (entry != null) {
if (entry.getName().endsWith(CLST_EXTENSION)) {
load(in);
}
entry = in.getNextEntry();
}
} finally {
in.close();
}
} else {
throw new JadxRuntimeException("Unknown file format: " + name);
}
}
public void load(InputStream input) throws IOException, DecodeException {
DataInputStream in = new DataInputStream(input);
byte[] header = new byte[JADX_CLS_SET_HEADER.length()];
int readHeaderLength = in.read(header);
int version = in.readByte();
if (readHeaderLength != JADX_CLS_SET_HEADER.length()
|| !JADX_CLS_SET_HEADER.equals(new String(header, STRING_CHARSET))
|| version != VERSION) {
throw new DecodeException("Wrong jadx class set header");
}
int count = in.readInt();
classes = new NClass[count];
for (int i = 0; i < count; i++) {
String name = readString(in);
classes[i] = new NClass(name, i);
}
for (int i = 0; i < count; i++) {
int pCount = in.readByte();
NClass[] parents = new NClass[pCount];
for (int j = 0; j < pCount; j++) {
parents[j] = classes[in.readInt()];
}
classes[i].setParents(parents);
}
}
private void writeString(DataOutputStream out, String name) throws IOException {
byte[] bytes = name.getBytes(STRING_CHARSET);
out.writeByte(bytes.length);
out.write(bytes);
}
private static String readString(DataInputStream in) throws IOException {
int len = in.readByte();
byte[] bytes = new byte[len];
int count = in.read(bytes);
while (count != len) {
int res = in.read(bytes, count, len - count);
if (res == -1) {
throw new IOException("String read error");
} else {
count += res;
}
}
return new String(bytes, STRING_CHARSET);
}
public int getClassesCount() {
return classes.length;
}
public void addToMap(Map<String, NClass> nameMap) {
for (NClass cls : classes) {
nameMap.put(cls.getName(), cls);
}
}
}
@@ -0,0 +1,113 @@
package jadx.core.clsp;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.exceptions.JadxRuntimeException;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Classes hierarchy graph
*/
public class ClspGraph {
private static final Logger LOG = LoggerFactory.getLogger(ClspGraph.class);
private final Map<String, Set<String>> ancestorCache = new WeakHashMap<String, Set<String>>();
private Map<String, NClass> nameMap;
public void load() throws IOException, DecodeException {
ClsSet set = new ClsSet();
set.load();
addClasspath(set);
}
public void addClasspath(ClsSet set) {
if (nameMap == null) {
nameMap = new HashMap<String, NClass>(set.getClassesCount());
set.addToMap(nameMap);
} else {
throw new JadxRuntimeException("Classpath already loaded");
}
}
public void addApp(List<ClassNode> classes) {
if (nameMap == null) {
throw new JadxRuntimeException("Classpath must be loaded first");
}
int size = classes.size();
NClass[] nClasses = new NClass[size];
for (int i = 0; i < size; i++) {
ClassNode cls = classes.get(i);
NClass nClass = new NClass(cls.getRawName(), -1);
nClasses[i] = nClass;
nameMap.put(cls.getRawName(), nClass);
}
for (int i = 0; i < size; i++) {
nClasses[i].setParents(ClsSet.makeParentsArray(classes.get(i), nameMap));
}
}
public boolean isImplements(String clsName, String implClsName) {
Set<String> anc = getAncestors(clsName);
return anc.contains(implClsName);
}
public String getCommonAncestor(String clsName, String implClsName) {
if (isImplements(clsName, implClsName)) {
return implClsName;
}
Set<String> anc = getAncestors(clsName);
NClass cls = nameMap.get(implClsName);
if (cls != null) {
return searchCommonParent(anc, cls);
} else {
LOG.debug("Missing class: {}", implClsName);
return null;
}
}
private String searchCommonParent(Set<String> anc, NClass cls) {
for (NClass p : cls.getParents()) {
String name = p.getName();
if (anc.contains(name)) {
return name;
} else {
String r = searchCommonParent(anc, p);
if (r != null)
return r;
}
}
return null;
}
private Set<String> getAncestors(String clsName) {
Set<String> result = ancestorCache.get(clsName);
if (result == null) {
result = new HashSet<String>();
ancestorCache.put(clsName, result);
NClass cls = nameMap.get(clsName);
if (cls != null) {
addAncestorsNames(cls, result);
} else {
LOG.debug("Missing class: {}", clsName);
}
}
return result;
}
private void addAncestorsNames(NClass cls, Set<String> result) {
result.add(cls.getName());
for (NClass p : cls.getParents()) {
addAncestorsNames(p, result);
}
}
}
@@ -0,0 +1,67 @@
package jadx.core.clsp;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.files.InputFile;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Utility class for convert dex or jar to jadx classes set (.jcst)
*/
public class ConvertToClsSet {
private static final Logger LOG = LoggerFactory.getLogger(ConvertToClsSet.class);
public static void usage() {
LOG.info("<output .jcst or .jar file> <several input dex or jar files> ");
}
public static void main(String[] args) throws IOException, DecodeException {
if (args.length < 2) {
usage();
System.exit(1);
}
File output = new File(args[0]);
List<InputFile> inputFiles = new ArrayList<InputFile>(args.length - 1);
for (int i = 1; i < args.length; i++) {
File f = new File(args[i]);
if (f.isDirectory()) {
addFilesFromDirectory(f, inputFiles);
} else {
inputFiles.add(new InputFile(f));
}
}
for (InputFile inputFile : inputFiles) {
LOG.info("Loaded: " + inputFile.getFile());
}
RootNode root = new RootNode();
root.load(inputFiles);
ClsSet set = new ClsSet();
set.load(root);
set.save(output);
LOG.info("Output: " + output);
LOG.info("done");
}
private static void addFilesFromDirectory(File dir, List<InputFile> inputFiles) throws IOException, DecodeException {
File[] files = dir.listFiles();
for (File file : files) {
if (file.isDirectory()) {
addFilesFromDirectory(file, inputFiles);
}
if (file.getName().endsWith(".dex")) {
inputFiles.add(new InputFile(file));
}
}
}
}
@@ -0,0 +1,55 @@
package jadx.core.clsp;
/**
* Class node in classpath graph
*/
public class NClass {
private final String name;
private NClass[] parents;
private int id;
public NClass(String name, int id) {
this.name = name;
this.id = id;
}
public String getName() {
return name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public NClass[] getParents() {
return parents;
}
public void setParents(NClass[] parents) {
this.parents = parents;
}
@Override
public int hashCode() {
return name.hashCode();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
NClass nClass = (NClass) o;
if (!name.equals(nClass.name)) return false;
return true;
}
@Override
public String toString() {
return name;
}
}
@@ -1,18 +1,18 @@
package jadx.codegen;
package jadx.core.codegen;
import jadx.Consts;
import jadx.dex.attributes.AttributeType;
import jadx.dex.attributes.IAttributeNode;
import jadx.dex.attributes.annotations.Annotation;
import jadx.dex.attributes.annotations.AnnotationsList;
import jadx.dex.attributes.annotations.MethodParameters;
import jadx.dex.info.FieldInfo;
import jadx.dex.instructions.args.ArgType;
import jadx.dex.nodes.ClassNode;
import jadx.dex.nodes.FieldNode;
import jadx.dex.nodes.MethodNode;
import jadx.utils.StringUtils;
import jadx.utils.exceptions.JadxRuntimeException;
import jadx.core.Consts;
import jadx.core.dex.attributes.AttributeType;
import jadx.core.dex.attributes.IAttributeNode;
import jadx.core.dex.attributes.annotations.Annotation;
import jadx.core.dex.attributes.annotations.AnnotationsList;
import jadx.core.dex.attributes.annotations.MethodParameters;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.StringUtils;
import jadx.core.utils.exceptions.JadxRuntimeException;
import java.util.Iterator;
import java.util.List;
@@ -81,7 +81,7 @@ public class AnnotationGen {
if (vl.size() == 1 && vl.containsKey("value")) {
code.add(encValueToString(vl.get("value")));
} else {
for (Iterator<Entry<String, Object>> it = vl.entrySet().iterator(); it.hasNext();) {
for (Iterator<Entry<String, Object>> it = vl.entrySet().iterator(); it.hasNext(); ) {
Entry<String, Object> e = it.next();
code.add(e.getKey());
code.add(" = ");
@@ -101,7 +101,7 @@ public class AnnotationGen {
if (an != null) {
Object exs = an.getDefaultValue();
code.add(" throws ");
for (Iterator<ArgType> it = ((List<ArgType>) exs).iterator(); it.hasNext();) {
for (Iterator<ArgType> it = ((List<ArgType>) exs).iterator(); it.hasNext(); ) {
ArgType ex = it.next();
code.add(TypeGen.translate(classGen, ex));
if (it.hasNext())
@@ -163,7 +163,7 @@ public class AnnotationGen {
StringBuilder str = new StringBuilder();
str.append('{');
List<Object> list = (List<Object>) val;
for (Iterator<Object> it = list.iterator(); it.hasNext();) {
for (Iterator<Object> it = list.iterator(); it.hasNext(); ) {
Object obj = it.next();
str.append(encValueToString(obj));
if (it.hasNext())
@@ -1,24 +1,25 @@
package jadx.codegen;
package jadx.core.codegen;
import jadx.Consts;
import jadx.dex.attributes.AttrNode;
import jadx.dex.attributes.AttributeFlag;
import jadx.dex.attributes.AttributeType;
import jadx.dex.attributes.EnumClassAttr;
import jadx.dex.attributes.EnumClassAttr.EnumField;
import jadx.dex.attributes.IAttribute;
import jadx.dex.attributes.SourceFileAttr;
import jadx.dex.info.AccessInfo;
import jadx.dex.info.ClassInfo;
import jadx.dex.instructions.args.ArgType;
import jadx.dex.instructions.args.InsnArg;
import jadx.dex.nodes.ClassNode;
import jadx.dex.nodes.FieldNode;
import jadx.dex.nodes.MethodNode;
import jadx.dex.nodes.parser.FieldValueAttr;
import jadx.utils.ErrorsCounter;
import jadx.utils.Utils;
import jadx.utils.exceptions.CodegenException;
import jadx.core.Consts;
import jadx.core.dex.attributes.AttrNode;
import jadx.core.dex.attributes.AttributeFlag;
import jadx.core.dex.attributes.AttributeType;
import jadx.core.dex.attributes.EnumClassAttr;
import jadx.core.dex.attributes.EnumClassAttr.EnumField;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.attributes.SourceFileAttr;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.DexNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.parser.FieldValueAttr;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.CodegenException;
import java.util.ArrayList;
import java.util.Collections;
@@ -29,9 +30,14 @@ 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;
@@ -59,19 +65,21 @@ public class ClassGen {
if (!"".equals(cls.getPackage())) {
clsCode.add("package ").add(cls.getPackage()).add(';');
clsCode.endl();
clsCode.newLine();
}
if (imports.size() != 0) {
List<String> sortImports = new ArrayList<String>();
for (ClassInfo ic : imports)
int importsCount = imports.size();
if (importsCount != 0) {
List<String> sortImports = new ArrayList<String>(importsCount);
for (ClassInfo ic : imports) {
sortImports.add(ic.getFullName());
}
Collections.sort(sortImports);
for (String imp : sortImports) {
clsCode.startLine("import ").add(imp).add(';');
}
clsCode.endl();
clsCode.newLine();
sortImports.clear();
imports.clear();
@@ -90,7 +98,7 @@ public class ClassGen {
makeClassDeclaration(code);
makeClassBody(code);
code.endl();
code.newLine();
}
public void makeClassDeclaration(CodeWriter clsCode) {
@@ -102,6 +110,7 @@ public class ClassGen {
}
annotationGen.addForClass(clsCode);
insertSourceFileInfo(clsCode, cls);
clsCode.startLine(af.makeString());
if (af.isInterface()) {
if (af.isAnnotation())
@@ -139,6 +148,8 @@ public class ClassGen {
if (!cls.getInterfaces().isEmpty())
clsCode.add(' ');
}
clsCode.attachAnnotation(cls);
}
public boolean makeGenericMap(CodeWriter code, Map<ArgType, List<ArgType>> gmap) {
@@ -172,19 +183,17 @@ public class ClassGen {
public void makeClassBody(CodeWriter clsCode) throws CodegenException {
clsCode.add('{');
insertSourceFileInfo(clsCode, cls);
CodeWriter mthsCode = makeMethods(clsCode, cls.getMethods());
CodeWriter fieldsCode = makeFields(clsCode, cls, cls.getFields());
clsCode.add(fieldsCode);
if (fieldsCode.notEmpty() && mthsCode.notEmpty())
clsCode.endl();
clsCode.newLine();
// insert inner classes code
if (cls.getInnerClasses().size() != 0) {
clsCode.add(makeInnerClasses(cls, clsCode.getIndent()));
if (mthsCode.notEmpty())
clsCode.endl();
clsCode.newLine();
}
clsCode.add(mthsCode);
clsCode.startLine('}');
@@ -227,19 +236,26 @@ public class ClassGen {
continue;
MethodGen mthGen = new MethodGen(this, mth);
mthGen.addDefinition(code);
code.add(" {");
if (mth.getAttributes().contains(AttributeFlag.INCONSISTENT_CODE)) {
code.startLine("/* JADX WARNING: inconsistent code */");
LOG.error(ErrorsCounter.formatErrorMsg(mth, " Inconsistent code"));
mthGen.makeMethodDump(code);
}
if (mthGen.addDefinition(code)) {
code.add(' ');
}
code.add('{');
insertSourceFileInfo(code, mth);
code.add(mthGen.makeInstructions(code.getIndent()));
code.startLine('}');
}
} catch (Throwable e) {
String msg = ErrorsCounter.methodError(mth, "Method generation error", e);
code.startLine("/* " + msg + CodeWriter.NL + Utils.getStackTrace(e) + "*/");
code.startLine("/* " + msg + CodeWriter.NL + Utils.getStackTrace(e) + " */");
}
if (it.hasNext())
code.endl();
code.newLine();
}
return code;
}
@@ -278,10 +294,13 @@ public class ClassGen {
code.startLine();
code.add(';');
code.endl();
code.newLine();
}
for (FieldNode f : fields) {
if(f.getAttributes().contains(AttributeFlag.DONT_GENERATE)) {
continue;
}
annotationGen.addForField(code, f);
code.startLine(f.getAccessFlags().makeString());
code.add(TypeGen.translate(this, f.getType()));
@@ -297,6 +316,7 @@ public class ClassGen {
}
}
code.add(';');
code.attachAnnotation(f);
}
return code;
}
@@ -309,7 +329,7 @@ public class ClassGen {
}
public String useClass(ClassInfo classInfo) {
String baseClass = useClassInternal(classInfo);
String baseClass = useClassInternal(cls.getClassInfo(), classInfo);
ArgType[] generics = classInfo.getType().getGenericTypes();
if (generics != null) {
StringBuilder sb = new StringBuilder();
@@ -333,47 +353,80 @@ public class ClassGen {
}
}
private String useClassInternal(ClassInfo classInfo) {
if (parentGen != null)
return parentGen.useClassInternal(classInfo);
private String useClassInternal(ClassInfo useCls, ClassInfo classInfo) {
String clsStr = classInfo.getFullName();
if (fallback)
if (fallback) {
return clsStr;
}
String shortName = classInfo.getShortName();
if (classInfo.getPackage().equals("java.lang") && classInfo.getParentClass() == null) {
return shortName;
} else {
// don't add import if this class inner for current class
if (isInner(classInfo, cls.getClassInfo()))
if (isClassInnerFor(classInfo, useCls)) {
return shortName;
}
// don't add import if this class from same package
if (classInfo.getPackage().equals(useCls.getPackage()) && !classInfo.isInner()) {
return shortName;
}
if (classInfo.getPackage().equals(useCls.getPackage())) {
clsStr = classInfo.getNameWithoutPackage();
}
if (searchCollision(cls.dex(), useCls, shortName)) {
return clsStr;
}
for (ClassInfo cls : imports) {
if (!cls.equals(classInfo)) {
if (cls.getShortName().equals(shortName))
if (cls.getShortName().equals(shortName)) {
return clsStr;
}
}
}
imports.add(classInfo);
addImport(classInfo);
return shortName;
}
}
private boolean isInner(ClassInfo inner, ClassInfo parent) {
private void addImport(ClassInfo classInfo) {
if (parentGen != null) {
parentGen.addImport(classInfo);
} else {
imports.add(classInfo);
}
}
private static boolean isClassInnerFor(ClassInfo inner, ClassInfo parent) {
if (inner.isInner()) {
ClassInfo p = inner.getParentClass();
return p.equals(parent) || isInner(p, parent);
return p.equals(parent) || isClassInnerFor(p, parent);
}
return false;
}
private static boolean searchCollision(DexNode dex, ClassInfo useCls, String shortName) {
if (useCls == null) {
return false;
}
if (useCls.getShortName().equals(shortName)) {
return true;
}
ClassNode classNode = dex.resolveClass(useCls);
if (classNode != null) {
for (ClassNode inner : classNode.getInnerClasses()) {
if (inner.getShortName().equals(shortName)) {
return true;
}
}
}
return searchCollision(dex, useCls.getParentClass(), shortName);
}
private void insertSourceFileInfo(CodeWriter code, AttrNode node) {
IAttribute sourceFileAttr = node.getAttributes().get(AttributeType.SOURCE_FILE);
if(sourceFileAttr != null) {
code.startLine(1, "// compiled from: ");
code.add(((SourceFileAttr)sourceFileAttr).getFileName());
if (sourceFileAttr != null) {
code.startLine("// compiled from: ");
code.add(((SourceFileAttr) sourceFileAttr).getFileName());
}
}
@@ -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;
}
@@ -1,9 +1,13 @@
package jadx.codegen;
package jadx.core.codegen;
import jadx.utils.exceptions.JadxRuntimeException;
import jadx.core.dex.attributes.LineAttrNode;
import jadx.core.utils.Utils;
import java.io.File;
import java.io.PrintWriter;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -15,10 +19,13 @@ public class CodeWriter {
public static final String NL = System.getProperty("line.separator");
private static final String INDENT = "\t";
private StringBuilder buf = new StringBuilder();
private final StringBuilder buf = new StringBuilder();
private String indentStr;
private int indent;
private int line = 1;
private Map<Object, Integer> annotations = Collections.emptyMap();
public CodeWriter() {
this.indent = 0;
this.indentStr = "";
@@ -29,32 +36,32 @@ public class CodeWriter {
updateIndent();
}
public CodeWriter startLine(String str) {
buf.append(NL);
public CodeWriter startLine() {
addLine();
buf.append(indentStr);
buf.append(str);
return this;
}
public CodeWriter startLine(char c) {
buf.append(NL);
addLine();
buf.append(indentStr);
buf.append(c);
return this;
}
public CodeWriter startLine(int ind, String str) {
buf.append(NL);
public CodeWriter startLine(String str) {
addLine();
buf.append(indentStr);
for (int i = 0; i < ind; i++)
buf.append(INDENT);
buf.append(str);
return this;
}
public CodeWriter startLine() {
buf.append(NL);
public CodeWriter startLine(int ind, String str) {
addLine();
buf.append(indentStr);
for (int i = 0; i < ind; i++)
buf.append(INDENT);
buf.append(str);
return this;
}
@@ -68,17 +75,47 @@ public class CodeWriter {
return this;
}
public CodeWriter add(CodeWriter mthsCode) {
buf.append(mthsCode.toString());
public CodeWriter add(CodeWriter code) {
line--;
for (Map.Entry<Object, Integer> entry : code.annotations.entrySet()) {
attachAnnotation(entry.getKey(), line + entry.getValue());
}
line += code.line;
buf.append(code.toString());
return this;
}
public CodeWriter endl() {
public CodeWriter newLine() {
addLine();
return this;
}
private void addLine() {
buf.append(NL);
line++;
}
public int getLine() {
return line;
}
public Object attachAnnotation(Object obj) {
return attachAnnotation(obj, line);
}
public Object attachAnnotation(Object obj, int line) {
if (annotations.isEmpty()) {
annotations = new HashMap<Object, Integer>();
}
return annotations.put(obj, line);
}
public CodeWriter indent() {
buf.append(indentStr);
return this;
}
private static final String[] INDENT_CACHE = new String[] {
private static final String[] INDENT_CACHE = new String[]{
"",
INDENT,
INDENT + INDENT,
@@ -126,6 +163,18 @@ public class CodeWriter {
updateIndent();
}
public void finish() {
buf.trimToSize();
for (Map.Entry<Object, Integer> entry : annotations.entrySet()) {
Object v = entry.getKey();
if (v instanceof LineAttrNode) {
LineAttrNode l = (LineAttrNode) v;
l.setDecompiledLine(entry.getValue());
}
}
annotations.clear();
}
private static String removeFirstEmptyLine(String str) {
if (str.startsWith(NL)) {
return str.substring(NL.length());
@@ -165,7 +214,7 @@ public class CodeWriter {
PrintWriter out = null;
try {
makeDirsForFile(file);
Utils.makeDirsForFile(file);
out = new PrintWriter(file, "UTF-8");
String code = buf.toString();
code = removeFirstEmptyLine(code);
@@ -175,17 +224,7 @@ public class CodeWriter {
} finally {
if (out != null)
out.close();
buf = null;
}
}
private void makeDirsForFile(File file) {
File dir = file.getParentFile();
if (!dir.exists()) {
// if directory already created in other thread mkdirs will return false,
// so check dir existence again
if (!dir.mkdirs() && !dir.exists())
throw new JadxRuntimeException("Can't create directory " + dir);
}
}
}
@@ -1,33 +1,41 @@
package jadx.codegen;
package jadx.core.codegen;
import jadx.dex.attributes.AttributeType;
import jadx.dex.attributes.IAttribute;
import jadx.dex.attributes.MethodInlineAttr;
import jadx.dex.info.ClassInfo;
import jadx.dex.info.FieldInfo;
import jadx.dex.info.MethodInfo;
import jadx.dex.instructions.ArithNode;
import jadx.dex.instructions.ArithOp;
import jadx.dex.instructions.FillArrayOp;
import jadx.dex.instructions.GotoNode;
import jadx.dex.instructions.IfNode;
import jadx.dex.instructions.IndexInsnNode;
import jadx.dex.instructions.InvokeNode;
import jadx.dex.instructions.InvokeType;
import jadx.dex.instructions.SwitchNode;
import jadx.dex.instructions.args.ArgType;
import jadx.dex.instructions.args.InsnArg;
import jadx.dex.instructions.args.InsnWrapArg;
import jadx.dex.instructions.args.LiteralArg;
import jadx.dex.instructions.args.RegisterArg;
import jadx.dex.instructions.mods.ConstructorInsn;
import jadx.dex.nodes.ClassNode;
import jadx.dex.nodes.FieldNode;
import jadx.dex.nodes.InsnNode;
import jadx.dex.nodes.MethodNode;
import jadx.dex.nodes.RootNode;
import jadx.utils.StringUtils;
import jadx.utils.exceptions.CodegenException;
import jadx.core.dex.attributes.AttributeFlag;
import jadx.core.dex.attributes.AttributeType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.attributes.MethodInlineAttr;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.ArithNode;
import jadx.core.dex.instructions.ArithOp;
import jadx.core.dex.instructions.ConstClassNode;
import jadx.core.dex.instructions.ConstStringNode;
import jadx.core.dex.instructions.FillArrayNode;
import jadx.core.dex.instructions.GotoNode;
import jadx.core.dex.instructions.IfNode;
import jadx.core.dex.instructions.IndexInsnNode;
import jadx.core.dex.instructions.InvokeNode;
import jadx.core.dex.instructions.InvokeType;
import jadx.core.dex.instructions.SwitchNode;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.FieldArg;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.InsnWrapArg;
import jadx.core.dex.instructions.args.LiteralArg;
import jadx.core.dex.instructions.args.NamedArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.mods.ConstructorInsn;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.InsnUtils;
import jadx.core.utils.RegionUtils;
import jadx.core.utils.StringUtils;
import jadx.core.utils.exceptions.CodegenException;
import java.util.ArrayList;
import java.util.EnumSet;
@@ -48,13 +56,14 @@ public class InsnGen {
protected final RootNode root;
private final boolean fallback;
public enum InsnGenState {
private static enum IGState {
SKIP,
NO_SEMICOLON,
NO_RESULT,
BODY_ONLY,
BODY_ONLY_NOWRAP,
}
public InsnGen(MethodGen mgen, MethodNode mth, boolean fallback) {
@@ -73,22 +82,36 @@ public class InsnGen {
}
public String arg(InsnArg arg) throws CodegenException {
return arg(arg, true);
}
public String arg(InsnArg arg, boolean wrap) throws CodegenException {
if (arg.isRegister()) {
return arg((RegisterArg) arg);
return mgen.makeArgName((RegisterArg) arg);
} else if (arg.isLiteral()) {
return lit((LiteralArg) arg);
} else {
} else if (arg.isInsnWrap()) {
CodeWriter code = new CodeWriter();
makeInsn(((InsnWrapArg) arg).getWrapInsn(), code, true);
IGState flag = wrap ? IGState.BODY_ONLY : IGState.BODY_ONLY_NOWRAP;
makeInsn(((InsnWrapArg) arg).getWrapInsn(), code, flag);
return code.toString();
} else if (arg.isNamed()) {
return ((NamedArg) arg).getName();
} else if (arg.isField()) {
FieldArg f = (FieldArg) arg;
if (f.isStatic()) {
return sfield(f.getField());
} else {
RegisterArg regArg = new RegisterArg(f.getRegNum());
regArg.replaceTypedVar(f);
return ifield(f.getField(), regArg);
}
} else {
throw new CodegenException("Unknown arg type " + arg);
}
}
public String arg(RegisterArg arg) {
return mgen.makeArgName(arg);
}
public String assignVar(InsnNode insn) {
public String assignVar(InsnNode insn) throws CodegenException {
RegisterArg arg = insn.getResult();
if (insn.getAttributes().contains(AttributeType.DECLARE_VARIABLE)) {
return declareVar(arg);
@@ -105,19 +128,43 @@ public class InsnGen {
return TypeGen.literalToString(arg.getLiteral(), arg.getType());
}
private String ifield(IndexInsnNode insn, int reg) throws CodegenException {
FieldInfo field = (FieldInfo) insn.getIndex();
return arg(insn.getArg(reg)) + '.' + field.getName();
private String ifield(FieldInfo field, InsnArg arg) throws CodegenException {
FieldNode fieldNode = mth.getParentClass().searchField(field);
if(fieldNode != null && fieldNode.getAttributes().contains(AttributeFlag.DONT_GENERATE)) {
return "";
}
String name = field.getName();
// TODO: add jadx argument "add this"
// FIXME: check variable names in scope
if (false && arg.isThis()) {
boolean useShort = true;
List<RegisterArg> args = mth.getArguments(false);
for (RegisterArg param : args) {
String paramName = param.getTypedVar().getName();
if (paramName != null && paramName.equals(name)) {
useShort = false;
}
}
if (useShort) {
return name;
}
}
String argStr = arg(arg);
return argStr.isEmpty() ? name : argStr + "." + name;
}
private String sfield(IndexInsnNode insn) {
FieldInfo field = (FieldInfo) insn.getIndex();
protected String sfield(FieldInfo field) {
String thisClass = mth.getParentClass().getFullName();
if (field.getDeclClass().getFullName().equals(thisClass)) {
ClassInfo declClass = field.getDeclClass();
if (thisClass.startsWith(declClass.getFullName())) {
return field.getName();
} else {
return useClass(field.getDeclClass()) + '.' + field.getName();
}
// Android specific resources class handler
ClassInfo parentClass = declClass.getParentClass();
if (parentClass != null && parentClass.getShortName().equals("R")) {
return useClass(parentClass) + "." + declClass.getShortName() + "." + field.getName();
}
return useClass(declClass) + '.' + field.getName();
}
private void fieldPut(IndexInsnNode insn) {
@@ -140,30 +187,34 @@ public class InsnGen {
}
public boolean makeInsn(InsnNode insn, CodeWriter code) throws CodegenException {
return makeInsn(insn, code, false);
return makeInsn(insn, code, null);
}
private boolean makeInsn(InsnNode insn, CodeWriter code, boolean bodyOnly) throws CodegenException {
private boolean makeInsn(InsnNode insn, CodeWriter code, IGState flag) throws CodegenException {
try {
EnumSet<InsnGenState> state = EnumSet.noneOf(InsnGenState.class);
if (bodyOnly) {
state.add(InsnGenState.BODY_ONLY);
EnumSet<IGState> state = EnumSet.noneOf(IGState.class);
if (flag == IGState.BODY_ONLY || flag == IGState.BODY_ONLY_NOWRAP) {
state.add(flag);
makeInsnBody(code, insn, state);
} else {
CodeWriter body = new CodeWriter(code.getIndent());
makeInsnBody(body, insn, state);
if (state.contains(InsnGenState.SKIP))
if (state.contains(IGState.SKIP)) {
return false;
}
if (insn.getResult() != null && !state.contains(InsnGenState.NO_RESULT))
code.startLine(assignVar(insn)).add(" = ");
else
code.startLine();
code.startLine();
if (insn.getSourceLine() != 0) {
code.attachAnnotation(insn.getSourceLine());
}
if (insn.getResult() != null && !state.contains(IGState.NO_RESULT)) {
code.add(assignVar(insn)).add(" = ");
}
code.add(body);
if (!state.contains(InsnGenState.NO_SEMICOLON))
if (!state.contains(IGState.NO_SEMICOLON)) {
code.add(';');
}
}
} catch (Throwable th) {
throw new CodegenException(mth, "Error generate insn: " + insn, th);
@@ -171,50 +222,56 @@ public class InsnGen {
return true;
}
private void makeInsnBody(CodeWriter code, InsnNode insn, EnumSet<InsnGenState> state) throws CodegenException {
private void makeInsnBody(CodeWriter code, InsnNode insn, EnumSet<IGState> state) throws CodegenException {
switch (insn.getType()) {
case CONST_STR:
String str = ((ConstStringNode) insn).getString();
code.add(StringUtils.unescapeString(str));
break;
case CONST_CLASS:
ArgType clsType = ((ConstClassNode) insn).getClsType();
code.add(useType(clsType)).add(".class");
break;
case CONST:
if (insn.getArgsCount() == 0) {
// const in 'index' - string or class
Object ind = ((IndexInsnNode) insn).getIndex();
if (ind instanceof String)
code.add(StringUtils.unescapeString(ind.toString()));
else if (ind instanceof ArgType)
code.add(useType((ArgType) ind)).add(".class");
} else {
LiteralArg arg = (LiteralArg) insn.getArg(0);
code.add(lit(arg));
}
LiteralArg arg = (LiteralArg) insn.getArg(0);
code.add(lit(arg));
break;
case MOVE:
code.add(arg(insn.getArg(0)));
code.add(arg(insn.getArg(0), false));
break;
case CHECK_CAST:
case CAST:
code.add("((");
case CAST: {
boolean wrap = state.contains(IGState.BODY_ONLY);
if (wrap)
code.add("(");
code.add("(");
code.add(useType(((ArgType) ((IndexInsnNode) insn).getIndex())));
code.add(") (");
code.add(") ");
code.add(arg(insn.getArg(0)));
code.add("))");
if (wrap)
code.add(")");
break;
}
case ARITH:
makeArith((ArithNode) insn, code, state);
break;
case NEG:
String base = "-" + arg(insn.getArg(0));
if (state.contains(InsnGenState.BODY_ONLY))
if (state.contains(IGState.BODY_ONLY)) {
code.add('(').add(base).add(')');
else
} else {
code.add(base);
}
break;
case RETURN:
if (insn.getArgsCount() != 0)
code.add("return ").add(arg(insn.getArg(0)));
code.add("return ").add(arg(insn.getArg(0), false));
else
code.add("return");
break;
@@ -228,7 +285,7 @@ public class InsnGen {
break;
case THROW:
code.add("throw ").add(arg(insn.getArg(0)));
code.add("throw ").add(arg(insn.getArg(0), true));
break;
case CMP_L:
@@ -236,11 +293,19 @@ public class InsnGen {
code.add(String.format("(%1$s > %2$s ? 1 : (%1$s == %2$s ? 0 : -1))", arg(insn, 0), arg(insn, 1)));
break;
case INSTANCE_OF:
code.add('(').add(arg(insn, 0)).add(" instanceof ")
.add(useType((ArgType) ((IndexInsnNode) insn).getIndex())).add(')');
case INSTANCE_OF: {
boolean wrap = state.contains(IGState.BODY_ONLY);
if (wrap) {
code.add('(');
}
code.add(arg(insn, 0));
code.add(" instanceof ");
code.add(useType((ArgType) ((IndexInsnNode) insn).getIndex()));
if (wrap) {
code.add(')');
}
break;
}
case CONSTRUCTOR:
makeConstructor((ConstructorInsn) insn, code, state);
break;
@@ -263,7 +328,7 @@ public class InsnGen {
break;
case FILL_ARRAY:
fillArray((FillArrayOp) insn, code);
fillArray((FillArrayNode) insn, code);
break;
case FILLED_NEW_ARRAY:
@@ -271,53 +336,62 @@ public class InsnGen {
break;
case AGET:
code.add(arg(insn.getArg(0))).add('[').add(arg(insn.getArg(1))).add(']');
code.add(arg(insn.getArg(0))).add('[').add(arg(insn.getArg(1), false)).add(']');
break;
case APUT:
code.add(arg(insn, 0)).add('[').add(arg(insn, 1)).add("] = ").add(arg(insn, 2));
code.add(arg(insn, 0)).add('[').add(arg(insn.getArg(1), false)).add("] = ").add(arg(insn.getArg(2), false));
break;
case IGET:
code.add(ifield((IndexInsnNode) insn, 0));
case IGET: {
FieldInfo fieldInfo = (FieldInfo) ((IndexInsnNode) insn).getIndex();
code.add(ifield(fieldInfo, insn.getArg(0)));
break;
case IPUT:
code.add(ifield((IndexInsnNode) insn, 1)).add(" = ").add(arg(insn.getArg(0)));
}
case IPUT: {
FieldInfo fieldInfo = (FieldInfo) ((IndexInsnNode) insn).getIndex();
code.add(ifield(fieldInfo, insn.getArg(1))).add(" = ").add(arg(insn.getArg(0), false));
break;
}
case SGET:
code.add(sfield((IndexInsnNode) insn));
code.add(sfield((FieldInfo) ((IndexInsnNode) insn).getIndex()));
break;
case SPUT:
IndexInsnNode node = (IndexInsnNode) insn;
fieldPut(node);
code.add(sfield(node)).add(" = ").add(arg(node.getArg(0)));
code.add(sfield((FieldInfo) node.getIndex())).add(" = ").add(arg(node.getArg(0), false));
break;
case STR_CONCAT:
// TODO: wrap in braces only if necessary
code.add('(');
StringBuilder sb = new StringBuilder();
for (Iterator<InsnArg> it = insn.getArguments().iterator(); it.hasNext(); ) {
code.add(arg(it.next()));
if (it.hasNext())
code.add(" + ");
sb.append(arg(it.next()));
if (it.hasNext()) {
sb.append(" + ");
}
}
// TODO: wrap in braces only if necessary
if (state.contains(IGState.BODY_ONLY)) {
code.add('(').add(sb.toString()).add(')');
} else {
code.add(sb.toString());
}
code.add(')');
break;
case MONITOR_ENTER:
if (isFallback()) {
code.add("monitor-enter(").add(arg(insn.getArg(0))).add(')');
} else {
state.add(InsnGenState.SKIP);
state.add(IGState.SKIP);
}
break;
case MONITOR_EXIT:
if (isFallback()) {
code.add("monitor-exit(").add(arg(insn.getArg(0))).add(')');
code.add("monitor-exit(").add(arg(insn, 0)).add(')');
} else {
state.add(InsnGenState.SKIP);
state.add(IGState.SKIP);
}
break;
@@ -325,11 +399,7 @@ public class InsnGen {
if (isFallback()) {
code.add("move-exception");
} else {
// don't have body
if (state.contains(InsnGenState.BODY_ONLY))
code.add(arg(insn.getResult()));
else
state.add(InsnGenState.SKIP);
code.add(arg(insn, 0));
}
break;
@@ -337,16 +407,16 @@ public class InsnGen {
break;
case ARGS:
code.add(arg(insn.getArg(0)));
code.add(arg(insn, 0));
break;
case NOP:
state.add(InsnGenState.SKIP);
state.add(IGState.SKIP);
break;
/* fallback mode instructions */
case IF:
assert isFallback();
assert isFallback() : "if insn in not fallback mode";
IfNode ifInsn = (IfNode) insn;
String cond = arg(insn.getArg(0)) + " " + ifInsn.getOp().getSymbol() + " "
+ (ifInsn.isZeroCmp() ? "0" : arg(insn.getArg(1)));
@@ -370,7 +440,7 @@ public class InsnGen {
code.startLine("default: goto " + MethodGen.getLabelName(sw.getDefaultCaseOffset()) + ";");
code.decIndent();
code.startLine('}');
state.add(InsnGenState.NO_SEMICOLON);
state.add(IGState.NO_SEMICOLON);
break;
case NEW_INSTANCE:
@@ -396,16 +466,24 @@ public class InsnGen {
code.add('}');
}
private void fillArray(FillArrayOp insn, CodeWriter code) throws CodegenException {
ArgType elType = insn.getResult().getType().getArrayElement();
if (elType.getPrimitiveType() == null) {
elType = elType.selectFirst();
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[]) insn.getData();
byte[] array = (byte[]) data;
for (byte b : array) {
str.append(TypeGen.literalToString(b, elType));
str.append(", ");
@@ -413,7 +491,7 @@ public class InsnGen {
break;
case SHORT:
case CHAR:
short[] sarray = (short[]) insn.getData();
short[] sarray = (short[]) data;
for (short b : sarray) {
str.append(TypeGen.literalToString(b, elType));
str.append(", ");
@@ -421,7 +499,7 @@ public class InsnGen {
break;
case INT:
case FLOAT:
int[] iarray = (int[]) insn.getData();
int[] iarray = (int[]) data;
for (int b : iarray) {
str.append(TypeGen.literalToString(b, elType));
str.append(", ");
@@ -429,7 +507,7 @@ public class InsnGen {
break;
case LONG:
case DOUBLE:
long[] larray = (long[]) insn.getData();
long[] larray = (long[]) data;
for (long b : larray) {
str.append(TypeGen.literalToString(b, elType));
str.append(", ");
@@ -441,44 +519,51 @@ public class InsnGen {
}
int len = str.length();
str.delete(len - 2, len);
code.add("new ").add(useType(elType)).add("[] { ").add(str.toString()).add(" }");
code.add("new ").add(useType(elType)).add("[]{").add(str.toString()).add('}');
}
private void makeConstructor(ConstructorInsn insn, CodeWriter code, EnumSet<InsnGenState> state)
private void makeConstructor(ConstructorInsn insn, CodeWriter code, EnumSet<IGState> state)
throws CodegenException {
ClassNode cls = root.resolveClass(insn.getClassType());
ClassNode cls = mth.dex().resolveClass(insn.getClassType());
if (cls != null && cls.isAnonymous()) {
// anonymous class construction
ClassInfo parent;
if (cls.getSuperClass() != null
&& !cls.getSuperClass().getFullName().equals("java.lang.Object"))
parent = cls.getSuperClass();
else
if (cls.getInterfaces().size() == 1) {
parent = cls.getInterfaces().get(0);
code.add("new ").add(useClass(parent)).add("()");
} else {
parent = cls.getSuperClass();
}
MethodNode defCtr = cls.getDefaultConstructor();
if (RegionUtils.notEmpty(defCtr.getRegion())) {
defCtr.getAttributes().add(AttributeFlag.ANONYMOUS_CONSTRUCTOR);
} else {
defCtr.getAttributes().add(AttributeFlag.DONT_GENERATE);
}
code.add("new ").add(parent == null ? "Object" : useClass(parent)).add("() ");
code.incIndent(2);
new ClassGen(cls, mgen.getClassGen().getParentGen(), fallback).makeClassBody(code);
code.decIndent(2);
} else if (insn.isSuper()) {
return;
}
if (insn.isSelf()) {
// skip
state.add(IGState.SKIP);
return;
}
if (insn.isSuper()) {
code.add("super");
addArgs(code, insn, 0);
} else if (insn.isThis()) {
code.add("this");
addArgs(code, insn, 0);
} else if (insn.isSelf()) {
// skip
state.add(InsnGenState.SKIP);
} else {
code.add("new ").add(useClass(insn.getClassType()));
addArgs(code, insn, 0);
}
generateArguments(code, insn, 0, mth.dex().resolveMethod(insn.getCallMth()));
}
private void makeInvoke(InvokeNode insn, CodeWriter code) throws CodegenException {
MethodInfo callMth = insn.getCallMth();
// inline method if METHOD_INLINE attribute is attached
// inline method
MethodNode callMthNode = mth.dex().resolveMethod(callMth);
if (callMthNode != null
&& callMthNode.getAttributes().contains(AttributeType.METHOD_INLINE)) {
@@ -492,7 +577,14 @@ public class InsnGen {
case DIRECT:
case VIRTUAL:
case INTERFACE:
code.add(arg(insn.getArg(0))).add('.');
InsnArg arg = insn.getArg(0);
// FIXME: add 'this' for equals methods in scope
if (!arg.isThis()) {
String argStr = arg(arg);
if(!argStr.isEmpty()) {
code.add(argStr).add('.');
}
}
k++;
break;
@@ -504,19 +596,58 @@ public class InsnGen {
case STATIC:
ClassInfo insnCls = mth.getParentClass().getClassInfo();
if (!insnCls.equals(callMth.getDeclClass()))
code.add(useClass(callMth.getDeclClass())).add('.');
ClassInfo declClass = callMth.getDeclClass();
if (!insnCls.equals(declClass)) {
code.add(useClass(declClass)).add('.');
}
break;
}
code.add(callMth.getName());
addArgs(code, insn, k);
generateArguments(code, insn, k, callMthNode);
}
private void generateArguments(CodeWriter code, InsnNode insn, int k, MethodNode callMth) throws CodegenException {
if (callMth != null && callMth.getAttributes().contains(AttributeFlag.SKIP_FIRST_ARG)) {
k++;
}
int argsCount = insn.getArgsCount();
if (callMth != null && callMth.isArgsOverload()) {
// add additional argument casts for overloaded methods
List<ArgType> originalType = callMth.getMethodInfo().getArgumentsTypes();
int origPos = 0;
code.add('(');
for (int i = k; i < argsCount; i++) {
InsnArg arg = insn.getArg(i);
ArgType origType = originalType.get(origPos);
if (!arg.getType().equals(origType)) {
code.add('(').add(useType(origType)).add(')').add(arg(arg, true));
} else {
code.add(arg(arg, false));
}
if (i < argsCount - 1) {
code.add(", ");
}
origPos++;
}
code.add(')');
} else {
code.add('(');
if (k < argsCount) {
code.add(arg(insn.getArg(k), false));
for (int i = k + 1; i < argsCount; i++) {
code.add(", ");
code.add(arg(insn.getArg(i), false));
}
}
code.add(')');
}
}
private void inlineMethod(MethodNode callMthNode, InvokeNode insn, CodeWriter code) throws CodegenException {
IAttribute mia = callMthNode.getAttributes().get(AttributeType.METHOD_INLINE);
InsnNode inl = ((MethodInlineAttr) mia).getInsn();
if (callMthNode.getMethodInfo().getArgumentsTypes().isEmpty()) {
makeInsn(inl, code, true);
makeInsn(inl, code, IGState.BODY_ONLY);
} else {
// remap args
InsnArg[] regs = new InsnArg[callMthNode.getRegsCount()];
@@ -543,7 +674,7 @@ public class InsnGen {
}
}
}
makeInsn(inl, code, true);
makeInsn(inl, code, IGState.BODY_ONLY);
// revert changes
for (Entry<RegisterArg, InsnArg> e : toRevert.entrySet()) {
inl.replaceArg(e.getValue(), e.getKey());
@@ -551,37 +682,29 @@ public class InsnGen {
}
}
private void addArgs(CodeWriter code, InsnNode insn, int k) throws CodegenException {
code.add('(');
for (int i = k; i < insn.getArgsCount(); i++) {
code.add(arg(insn, i));
if (i < insn.getArgsCount() - 1)
code.add(", ");
}
code.add(')');
}
private void makeArith(ArithNode insn, CodeWriter code, EnumSet<InsnGenState> state) throws CodegenException {
private void makeArith(ArithNode insn, CodeWriter code, EnumSet<IGState> state) throws CodegenException {
ArithOp op = insn.getOp();
String v1 = arg(insn.getArg(0));
String v2 = arg(insn.getArg(1));
if (state.contains(InsnGenState.BODY_ONLY)) {
if (state.contains(IGState.BODY_ONLY)) {
// wrap insn in brackets for save correct operation order
// TODO don't wrap first insn in wrapped stack
code.add('(').add(v1).add(' ').add(op.getSymbol()).add(' ').add(v2).add(')');
} else if (state.contains(IGState.BODY_ONLY_NOWRAP)) {
code.add(v1).add(' ').add(op.getSymbol()).add(' ').add(v2);
} else {
String res = arg(insn.getResult());
if (res.equals(v1) && insn.getResult().equals(insn.getArg(0))) {
state.add(InsnGenState.NO_RESULT);
state.add(IGState.NO_RESULT);
// "++" or "--"
if (insn.getArg(1).isLiteral() && (op == ArithOp.ADD || op == ArithOp.SUB)) {
LiteralArg lit = (LiteralArg) insn.getArg(1);
if (Math.abs(lit.getLiteral()) == 1 && lit.isInteger()) {
if (lit.isInteger() && lit.getLiteral() == 1) {
code.add(assignVar(insn)).add(op.getSymbol()).add(op.getSymbol());
return;
}
}
// +=, -= ...
v2 = arg(insn.getArg(1), false);
code.add(assignVar(insn)).add(' ').add(op.getSymbol()).add("= ").add(v2);
} else {
code.add(v1).add(' ').add(op.getSymbol()).add(' ').add(v2);
@@ -1,24 +1,25 @@
package jadx.codegen;
package jadx.core.codegen;
import jadx.Consts;
import jadx.dex.attributes.AttributeFlag;
import jadx.dex.attributes.AttributeType;
import jadx.dex.attributes.AttributesList;
import jadx.dex.attributes.JadxErrorAttr;
import jadx.dex.attributes.annotations.MethodParameters;
import jadx.dex.info.AccessInfo;
import jadx.dex.instructions.args.ArgType;
import jadx.dex.instructions.args.RegisterArg;
import jadx.dex.nodes.InsnNode;
import jadx.dex.nodes.MethodNode;
import jadx.dex.trycatch.CatchAttr;
import jadx.dex.visitors.DepthTraverser;
import jadx.dex.visitors.FallbackModeVisitor;
import jadx.utils.ErrorsCounter;
import jadx.utils.InsnUtils;
import jadx.utils.Utils;
import jadx.utils.exceptions.CodegenException;
import jadx.utils.exceptions.DecodeException;
import jadx.core.Consts;
import jadx.core.dex.attributes.AttributeFlag;
import jadx.core.dex.attributes.AttributeType;
import jadx.core.dex.attributes.AttributesList;
import jadx.core.dex.attributes.JadxErrorAttr;
import jadx.core.dex.attributes.annotations.MethodParameters;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.NamedArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.trycatch.CatchAttr;
import jadx.core.dex.visitors.DepthTraverser;
import jadx.core.dex.visitors.FallbackModeVisitor;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.InsnUtils;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.CodegenException;
import jadx.core.utils.exceptions.DecodeException;
import java.util.HashSet;
import java.util.Iterator;
@@ -56,53 +57,63 @@ public class MethodGen {
return classGen;
}
public void addDefinition(CodeWriter code) {
public boolean addDefinition(CodeWriter code) {
if (mth.getMethodInfo().isClassInit()) {
code.startLine("static");
} else {
if (mth.getAttributes().contains(AttributeFlag.INCONSISTENT_CODE)
&& !mth.getAttributes().contains(AttributeType.JADX_ERROR)) {
code.startLine("// jadx: inconsistent code");
}
annotationGen.addForMethod(code, mth);
AccessInfo ai = mth.getAccessFlags();
// don't add 'abstract' to methods in interface
if (mth.getParentClass().getAccessFlags().isInterface()) {
ai = ai.remove(AccessFlags.ACC_ABSTRACT);
}
code.startLine(ai.makeString());
if (classGen.makeGenericMap(code, mth.getGenericMap()))
code.add(' ');
if (mth.getAccessFlags().isConstructor()) {
code.add(classGen.getClassNode().getShortName()); // constructor
} else {
code.add(TypeGen.translate(classGen, mth.getReturnType()));
code.add(' ');
code.add(mth.getName());
}
code.add('(');
List<RegisterArg> args = mth.getArguments(false);
if (mth.getMethodInfo().isConstructor()
&& mth.getParentClass().getAttributes().contains(AttributeType.ENUM_CLASS)) {
if (args.size() == 2)
args.clear();
else if (args.size() > 2)
args = args.subList(2, args.size());
else
LOG.warn(ErrorsCounter.formatErrorMsg(mth,
"Incorrect number of args for enum constructor: " + args.size()
+ " (expected >= 2)"));
}
code.add(makeArguments(args));
code.add(")");
annotationGen.addThrows(mth, code);
code.attachAnnotation(mth);
return true;
}
if (mth.getAttributes().contains(AttributeFlag.ANONYMOUS_CONSTRUCTOR)) {
// don't add method name and arguments
code.startLine();
code.attachAnnotation(mth);
return false;
}
annotationGen.addForMethod(code, mth);
AccessInfo clsAccFlags = mth.getParentClass().getAccessFlags();
AccessInfo ai = mth.getAccessFlags();
// don't add 'abstract' to methods in interface
if (clsAccFlags.isInterface()) {
ai = ai.remove(AccessFlags.ACC_ABSTRACT);
}
// don't add 'public' for annotations
if (clsAccFlags.isAnnotation()) {
ai = ai.remove(AccessFlags.ACC_PUBLIC);
}
code.startLine(ai.makeString());
if (classGen.makeGenericMap(code, mth.getGenericMap())) {
code.add(' ');
}
if (mth.getAccessFlags().isConstructor()) {
code.add(classGen.getClassNode().getShortName()); // constructor
} else {
code.add(TypeGen.translate(classGen, mth.getReturnType()));
code.add(' ');
code.add(mth.getName());
}
code.add('(');
List<RegisterArg> args = mth.getArguments(false);
if (mth.getMethodInfo().isConstructor()
&& mth.getParentClass().getAttributes().contains(AttributeType.ENUM_CLASS)) {
if (args.size() == 2) {
args.clear();
} else if (args.size() > 2) {
args = args.subList(2, args.size());
} else {
LOG.warn(ErrorsCounter.formatErrorMsg(mth,
"Incorrect number of args for enum constructor: " + args.size()
+ " (expected >= 2)"));
}
}
code.add(makeArguments(args));
code.add(")");
annotationGen.addThrows(mth, code);
code.attachAnnotation(mth);
return true;
}
public CodeWriter makeArguments(List<RegisterArg> args) {
@@ -112,7 +123,7 @@ public class MethodGen {
(MethodParameters) mth.getAttributes().get(AttributeType.ANNOTATION_MTH_PARAMETERS);
int i = 0;
for (Iterator<RegisterArg> it = args.iterator(); it.hasNext();) {
for (Iterator<RegisterArg> it = args.iterator(); it.hasNext(); ) {
RegisterArg arg = it.next();
// add argument annotation
@@ -165,24 +176,24 @@ public class MethodGen {
else
return name;
} else {
return base + "_" + Utils.escape(TypeGen.translate(classGen, arg.getType()));
ArgType type = arg.getType();
if (type.isPrimitive())
return base + type.getPrimitiveType().getShortName().toLowerCase();
else
return base + "_" + Utils.escape(TypeGen.translate(classGen, arg.getType()));
}
}
}
/**
* Put variable declaration and return variable name (used for assignments)
*
* @param arg
* register variable
*
* @param arg register variable
* @return variable name
*/
public String assignArg(RegisterArg arg) {
String name = makeArgName(arg);
if (varNames.add(name))
return name;
if (fallback)
if (varNames.add(name) || fallback)
return name;
name = getUniqVarName(name);
@@ -190,6 +201,16 @@ public class MethodGen {
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;
@@ -201,13 +222,6 @@ public class MethodGen {
return r;
}
private void makeInitCode(CodeWriter code) throws CodegenException {
InsnGen igen = new InsnGen(this, mth, fallback);
// generate super call
if (mth.getSuperCall() != null)
igen.makeInsn(mth.getSuperCall(), code);
}
public CodeWriter makeInstructions(int mthIndent) throws CodegenException {
CodeWriter code = new CodeWriter(mthIndent + 1);
@@ -220,22 +234,16 @@ public class MethodGen {
code.startLine("// jadx: method processing error");
Throwable cause = err.getCause();
if (cause != null) {
code.endl();
code.newLine();
code.add("/*");
code.startLine("Error: ").add(Utils.getStackTrace(cause));
code.add("*/");
}
makeMethodDump(code, mth);
makeMethodDump(code);
} else {
if (mth.getRegion() != null) {
CodeWriter insns = new CodeWriter(mthIndent + 1);
(new RegionGen(this, mth)).makeRegion(insns, mth.getRegion());
if (mth.getAttributes().contains(AttributeFlag.INCONSISTENT_CODE)) {
LOG.debug(ErrorsCounter.formatErrorMsg(mth, " Inconsistent code"));
// makeMethodDump(code, mth);
}
makeInitCode(code);
code.add(insns);
} else {
makeFallbackMethod(code, mth);
@@ -244,7 +252,7 @@ public class MethodGen {
return code;
}
public void makeMethodDump(CodeWriter code, MethodNode mth) {
public void makeMethodDump(CodeWriter code) {
code.startLine("/*");
getFallbackMethodGen(mth).addDefinition(code);
code.add(" {");
@@ -259,13 +267,13 @@ public class MethodGen {
private void makeFallbackMethod(CodeWriter code, MethodNode mth) {
if (mth.getInstructions() == null) {
// load original instructions
// loadFile original instructions
try {
mth.load();
DepthTraverser.visit(new FallbackModeVisitor(), mth);
} catch (DecodeException e) {
// ignore
code.startLine("Can't load method instructions");
code.startLine("Can't loadFile method instructions");
return;
}
}
@@ -289,9 +297,9 @@ public class MethodGen {
}
try {
if (insnGen.makeInsn(insn, code)) {
CatchAttr _catch = (CatchAttr) attrs.get(AttributeType.CATCH_BLOCK);
if (_catch != null)
code.add("\t //" + _catch);
CatchAttr catchAttr = (CatchAttr) attrs.get(AttributeType.CATCH_BLOCK);
if (catchAttr != null)
code.add("\t //" + catchAttr);
}
} catch (CodegenException e) {
code.startLine("// error: " + insn);
@@ -1,33 +1,39 @@
package jadx.codegen;
package jadx.core.codegen;
import jadx.dex.attributes.AttributeFlag;
import jadx.dex.attributes.AttributeType;
import jadx.dex.attributes.DeclareVariableAttr;
import jadx.dex.attributes.ForceReturnAttr;
import jadx.dex.attributes.IAttribute;
import jadx.dex.instructions.IfNode;
import jadx.dex.instructions.IfOp;
import jadx.dex.instructions.SwitchNode;
import jadx.dex.instructions.args.ArgType;
import jadx.dex.instructions.args.InsnArg;
import jadx.dex.instructions.args.LiteralArg;
import jadx.dex.instructions.args.RegisterArg;
import jadx.dex.nodes.IBlock;
import jadx.dex.nodes.IContainer;
import jadx.dex.nodes.IRegion;
import jadx.dex.nodes.InsnNode;
import jadx.dex.nodes.MethodNode;
import jadx.dex.regions.IfRegion;
import jadx.dex.regions.LoopRegion;
import jadx.dex.regions.Region;
import jadx.dex.regions.SwitchRegion;
import jadx.dex.regions.SynchronizedRegion;
import jadx.dex.trycatch.CatchAttr;
import jadx.dex.trycatch.ExceptionHandler;
import jadx.dex.trycatch.TryCatchBlock;
import jadx.utils.ErrorsCounter;
import jadx.utils.RegionUtils;
import jadx.utils.exceptions.CodegenException;
import jadx.core.dex.attributes.AttributeFlag;
import jadx.core.dex.attributes.AttributeType;
import jadx.core.dex.attributes.DeclareVariableAttr;
import jadx.core.dex.attributes.ForceReturnAttr;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.instructions.ArithNode;
import jadx.core.dex.instructions.IfOp;
import jadx.core.dex.instructions.IndexInsnNode;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.SwitchNode;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.InsnWrapArg;
import jadx.core.dex.instructions.args.LiteralArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.IBlock;
import jadx.core.dex.nodes.IContainer;
import jadx.core.dex.nodes.IRegion;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.regions.IfCondition;
import jadx.core.dex.regions.IfRegion;
import jadx.core.dex.regions.LoopRegion;
import jadx.core.dex.regions.Region;
import jadx.core.dex.regions.SwitchRegion;
import jadx.core.dex.regions.SynchronizedRegion;
import jadx.core.dex.trycatch.CatchAttr;
import jadx.core.dex.trycatch.ExceptionHandler;
import jadx.core.dex.trycatch.TryCatchBlock;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.RegionUtils;
import jadx.core.utils.exceptions.CodegenException;
import java.util.List;
@@ -104,8 +110,7 @@ public class RegionGen extends InsnGen {
}
private void makeIf(IfRegion region, CodeWriter code) throws CodegenException {
IfNode insn = region.getIfInsn();
code.add("if ").add(makeCondition(insn)).add(" {");
code.add("if (").add(makeCondition(region.getCondition())).add(") {");
makeRegionIndent(code, region.getThenRegion());
code.startLine('}');
@@ -116,9 +121,9 @@ public class RegionGen extends InsnGen {
// connect if-else-if block
if (els instanceof Region) {
Region re = (Region) els;
if (re.getSubBlocks().size() == 1
&& re.getSubBlocks().get(0) instanceof IfRegion) {
makeIf((IfRegion) re.getSubBlocks().get(0), code);
List<IContainer> subBlocks = re.getSubBlocks();
if (subBlocks.size() == 1 && subBlocks.get(0) instanceof IfRegion) {
makeIf((IfRegion) subBlocks.get(0), code);
return;
}
}
@@ -130,7 +135,21 @@ public class RegionGen extends InsnGen {
}
private CodeWriter makeLoop(LoopRegion region, CodeWriter code) throws CodegenException {
if (region.getConditionBlock() == null) {
BlockNode header = region.getHeader();
if (header != null) {
List<InsnNode> headerInsns = header.getInstructions();
if (headerInsns.size() > 1) {
// write not inlined instructions from header
mth.getAttributes().add(AttributeFlag.INCONSISTENT_CODE);
for (int i = 0; i < headerInsns.size() - 1; i++) {
InsnNode insn = headerInsns.get(i);
makeInsn(insn, code);
}
}
}
IfCondition condition = region.getCondition();
if (condition == null) {
// infinite loop
code.startLine("while (true) {");
makeRegionIndent(code, region.getBody());
@@ -138,15 +157,14 @@ public class RegionGen extends InsnGen {
return code;
}
IfNode insn = region.getIfInsn();
if (!region.isConditionAtEnd()) {
code.startLine("while ").add(makeCondition(insn)).add(" {");
makeRegionIndent(code, region.getBody());
code.startLine('}');
} else {
if (region.isConditionAtEnd()) {
code.startLine("do {");
makeRegionIndent(code, region.getBody());
code.startLine("} while ").add(makeCondition(insn)).add(';');
code.startLine("} while (").add(makeCondition(condition)).add(");");
} else {
code.startLine("while (").add(makeCondition(condition)).add(") {");
makeRegionIndent(code, region.getBody());
code.startLine('}');
}
return code;
}
@@ -157,59 +175,92 @@ public class RegionGen extends InsnGen {
code.startLine('}');
}
private String makeCondition(IfNode insn) throws CodegenException {
String simple = simplifyCondition(insn);
if (simple != null)
return simple;
String second;
if (insn.isZeroCmp()) {
second = arg(InsnArg.lit(0, insn.getArg(0).getType()));
} else {
second = arg(insn.getArg(1));
private String makeCondition(IfCondition condition) throws CodegenException {
switch (condition.getMode()) {
case COMPARE:
return makeCompare(condition.getCompare());
case NOT:
return "!" + makeCondition(condition.getArgs().get(0));
case AND:
case OR:
String mode = condition.getMode() == IfCondition.Mode.AND ? " && " : " || ";
StringBuilder sb = new StringBuilder();
for (IfCondition arg : condition.getArgs()) {
if (sb.length() != 0) {
sb.append(mode);
}
String s = makeCondition(arg);
if (arg.isCompare()) {
sb.append(s);
} else {
sb.append('(').append(s).append(')');
}
}
return sb.toString();
default:
return "??" + condition.toString();
}
return "(" + arg(insn.getArg(0)) + " " + insn.getOp().getSymbol() + " " + second + ")";
}
private String simplifyCondition(IfNode insn) throws CodegenException {
InsnArg firstArg = insn.getArg(0);
if (firstArg.getType().equals(ArgType.BOOLEAN)) {
IfOp op = insn.getOp();
if (insn.isZeroCmp()) {
private String makeCompare(IfCondition.Compare compare) throws CodegenException {
IfOp op = compare.getOp();
InsnArg firstArg = compare.getA();
InsnArg secondArg = compare.getB();
if (firstArg.getType().equals(ArgType.BOOLEAN)
&& secondArg.isLiteral()
&& secondArg.getType().equals(ArgType.BOOLEAN)) {
LiteralArg lit = (LiteralArg) secondArg;
if (lit.getLiteral() == 0) {
op = op.invert();
} else {
InsnArg secondArg = insn.getArg(1);
if (!secondArg.isLiteral() || !secondArg.getType().equals(ArgType.BOOLEAN))
return null;
LiteralArg lit = (LiteralArg) secondArg;
if (lit.getLiteral() == 0)
op = op.invert();
}
if (op == IfOp.EQ) {
return "(" + arg(firstArg) + ")"; // == true
return arg(firstArg, false); // == true
} else if (op == IfOp.NE) {
return "(!" + arg(firstArg) + ")"; // != true
return "!" + arg(firstArg); // != true
}
LOG.warn(ErrorsCounter.formatErrorMsg(mth, "Unsupported boolean condition " + op.getSymbol()));
}
return null;
return arg(firstArg, isWrapNeeded(firstArg))
+ " " + op.getSymbol() + " "
+ arg(secondArg, isWrapNeeded(secondArg));
}
private boolean isWrapNeeded(InsnArg arg) {
if (!arg.isInsnWrap()) {
return false;
}
InsnNode insn = ((InsnWrapArg) arg).getWrapInsn();
if (insn.getType() == InsnType.ARITH) {
ArithNode arith = ((ArithNode) insn);
switch (arith.getOp()) {
case ADD:
case SUB:
case MUL:
case DIV:
case REM:
return false;
}
}
return true;
}
private CodeWriter makeSwitch(SwitchRegion sw, CodeWriter code) throws CodegenException {
SwitchNode insn = (SwitchNode) sw.getHeader().getInstructions().get(0);
InsnArg arg = insn.getArg(0);
code.startLine("switch(").add(arg(arg)).add(") {");
code.incIndent();
int size = sw.getKeys().size();
for (int i = 0; i < size; i++) {
List<Integer> keys = sw.getKeys().get(i);
List<Object> keys = sw.getKeys().get(i);
IContainer c = sw.getCases().get(i);
for (Integer k : keys) {
for (Object k : keys) {
code.startLine("case ");
code.add(TypeGen.literalToString(k, arg.getType()));
if (k instanceof IndexInsnNode) {
code.add(sfield((FieldInfo) ((IndexInsnNode) k).getIndex()));
}
else {
code.add(TypeGen.literalToString((Integer) k, arg.getType()));
}
code.add(':');
}
makeCaseBlock(c, code);
@@ -218,13 +269,12 @@ public class RegionGen extends InsnGen {
code.startLine("default:");
makeCaseBlock(sw.getDefaultCase(), code);
}
code.decIndent();
code.startLine('}');
return code;
}
private void makeCaseBlock(IContainer c, CodeWriter code) throws CodegenException {
code.add(" {");
if (RegionUtils.notEmpty(c)) {
makeRegionIndent(code, c);
if (RegionUtils.hasExitEdge(c)) {
@@ -233,7 +283,6 @@ public class RegionGen extends InsnGen {
} else {
code.startLine(1, "break;");
}
code.startLine('}');
}
private void makeTryCatch(IContainer region, TryCatchBlock tryCatchBlock, CodeWriter code)
@@ -268,10 +317,9 @@ public class RegionGen extends InsnGen {
code.startLine("} catch (");
code.add(handler.isCatchAll() ? "Throwable" : useClass(handler.getCatchType()));
code.add(' ');
code.add(mgen.assignArg(handler.getArg()));
code.add(mgen.assignNamedArg(handler.getArg()));
code.add(") {");
makeRegionIndent(code, region);
}
}
}
@@ -1,10 +1,10 @@
package jadx.codegen;
package jadx.core.codegen;
import jadx.dex.instructions.args.ArgType;
import jadx.dex.instructions.args.PrimitiveType;
import jadx.utils.StringUtils;
import jadx.utils.Utils;
import jadx.utils.exceptions.JadxRuntimeException;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.PrimitiveType;
import jadx.core.utils.StringUtils;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxRuntimeException;
public class TypeGen {
@@ -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;
}
@@ -106,9 +106,9 @@ public class TypeGen {
}
private static String wrapNegNum(boolean lz, String str) {
if (lz)
return "(" + str + ")";
else
// if (lz)
// return "(" + str + ")";
// else
return str;
}
}
@@ -1,4 +1,4 @@
package jadx.deobf;
package jadx.core.deobf;
import java.util.Arrays;
import java.util.HashSet;
@@ -6,8 +6,8 @@ import java.util.Set;
public class NameMapper {
private static final Set<String> reservedNames = new HashSet<String>(
Arrays.asList(new String[] {
private static final Set<String> RESERVED_NAMES = new HashSet<String>(
Arrays.asList(new String[]{
"abstract",
"assert",
"boolean",
@@ -21,7 +21,7 @@ public class NameMapper {
"continue",
"default",
"do",
"double ",
"double",
"else",
"enum",
"extends",
@@ -64,7 +64,7 @@ public class NameMapper {
}));
public static boolean isReserved(String str) {
return reservedNames.contains(str);
return RESERVED_NAMES.contains(str);
}
}
@@ -1,4 +1,4 @@
package jadx.dex.attributes;
package jadx.core.dex.attributes;
public abstract class AttrNode implements IAttributeNode {
@@ -1,4 +1,4 @@
package jadx.dex.attributes;
package jadx.core.dex.attributes;
public enum AttributeFlag {
TRY_ENTER,
@@ -14,6 +14,10 @@ public enum AttributeFlag {
DONT_SHRINK,
DONT_GENERATE,
SKIP,
SKIP_FIRST_ARG,
ANONYMOUS_CONSTRUCTOR,
INCONSISTENT_CODE, // warning about incorrect decompilation
}
@@ -1,4 +1,4 @@
package jadx.dex.attributes;
package jadx.core.dex.attributes;
public enum AttributeType {
@@ -29,7 +29,7 @@ public enum AttributeType {
DECLARE_VARIABLE(true);
private static final int notUniqCount;
private static final int NOT_UNIQ_COUNT;
private final boolean uniq;
static {
@@ -41,11 +41,11 @@ public enum AttributeType {
if (type.notUniq())
last = i;
}
notUniqCount = last + 1;
NOT_UNIQ_COUNT = last + 1;
}
public static int getNotUniqCount() {
return notUniqCount;
return NOT_UNIQ_COUNT;
}
private AttributeType(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;
@@ -129,7 +129,7 @@ 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)
it.remove();
@@ -148,7 +148,7 @@ public final class AttributesList {
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();
@@ -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) {
@@ -1,7 +1,7 @@
package jadx.dex.attributes;
package jadx.core.dex.attributes;
import jadx.dex.instructions.args.RegisterArg;
import jadx.utils.Utils;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.utils.Utils;
import java.util.List;
@@ -1,9 +1,9 @@
package jadx.dex.attributes;
package jadx.core.dex.attributes;
import jadx.dex.instructions.args.InsnArg;
import jadx.dex.nodes.ClassNode;
import jadx.dex.nodes.MethodNode;
import jadx.utils.Utils;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.Utils;
import java.util.ArrayList;
import java.util.Collections;
@@ -1,7 +1,7 @@
package jadx.dex.attributes;
package jadx.core.dex.attributes;
import jadx.dex.nodes.InsnNode;
import jadx.utils.Utils;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.utils.Utils;
public class ForceReturnAttr implements IAttribute {
@@ -1,4 +1,4 @@
package jadx.dex.attributes;
package jadx.core.dex.attributes;
public interface IAttribute {
@@ -1,4 +1,4 @@
package jadx.dex.attributes;
package jadx.core.dex.attributes;
public interface IAttributeNode {
@@ -1,6 +1,6 @@
package jadx.dex.attributes;
package jadx.core.dex.attributes;
import jadx.utils.Utils;
import jadx.core.utils.Utils;
public class JadxErrorAttr implements IAttribute {
@@ -1,6 +1,6 @@
package jadx.dex.attributes;
package jadx.core.dex.attributes;
import jadx.utils.InsnUtils;
import jadx.core.utils.InsnUtils;
public class JumpAttribute implements IAttribute {
@@ -0,0 +1,24 @@
package jadx.core.dex.attributes;
public abstract class LineAttrNode extends AttrNode {
private int sourceLine;
private int decompiledLine;
public int getSourceLine() {
return sourceLine;
}
public void setSourceLine(int sourceLine) {
this.sourceLine = sourceLine;
}
public int getDecompiledLine() {
return decompiledLine;
}
public void setDecompiledLine(int decompiledLine) {
this.decompiledLine = decompiledLine;
}
}
@@ -1,7 +1,7 @@
package jadx.dex.attributes;
package jadx.core.dex.attributes;
import jadx.dex.nodes.BlockNode;
import jadx.utils.BlockUtils;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.utils.BlockUtils;
import java.util.Collections;
import java.util.HashSet;
@@ -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;
@@ -93,8 +93,20 @@ 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() {
@@ -122,10 +134,7 @@ public class AccessInfo {
switch (type) {
case METHOD:
if ((accFlags & AccessFlags.ACC_SYNCHRONIZED) != 0)
code.append("synchronized ");
if ((accFlags & AccessFlags.ACC_DECLARED_SYNCHRONIZED) != 0)
if (isSynchronized())
code.append("synchronized ");
if (isBridge())
@@ -138,10 +147,10 @@ public class AccessInfo {
break;
case FIELD:
if ((accFlags & AccessFlags.ACC_VOLATILE) != 0)
if (isVolatile())
code.append("volatile ");
if ((accFlags & AccessFlags.ACC_TRANSIENT) != 0)
if (isTransient())
code.append("transient ");
break;
@@ -165,8 +174,21 @@ public class AccessInfo {
return code.toString();
}
public String rawString() {
switch (type){
case CLASS:
return AccessFlags.classString(accFlags);
case FIELD:
return AccessFlags.fieldString(accFlags);
case METHOD:
return AccessFlags.methodString(accFlags);
default:
return "?";
}
}
@Override
public String toString() {
return "AccessInfo: " + type + " " + 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;
@@ -80,8 +80,8 @@ public final class ClassInfo {
char firstChar = name.charAt(0);
if (Character.isDigit(firstChar)) {
name = "InnerClass_" + name;
} else if(firstChar == '$') {
name = Consts.ANONYMOUS_CLASS_PREFIX + name;
} else if (firstChar == '$') {
name = "_" + name;
}
if (NameMapper.isReserved(name)) {
@@ -101,10 +101,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,12 +143,12 @@ public final class ClassInfo {
@Override
public String toString() {
return getFullName();
return fullName;
}
@Override
public int hashCode() {
return type.hashCode();
return fullName.hashCode();
}
@Override
@@ -1,7 +1,7 @@
package jadx.dex.info;
package jadx.core.dex.info;
import jadx.dex.instructions.args.ArgType;
import jadx.dex.nodes.DexNode;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.DexNode;
import com.android.dx.io.FieldId;
@@ -39,6 +39,26 @@ public class FieldInfo {
return declClass;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
FieldInfo fieldInfo = (FieldInfo) o;
if (!name.equals(fieldInfo.name)) return false;
if (!type.equals(fieldInfo.type)) return false;
if (!declClass.equals(fieldInfo.declClass)) return false;
return true;
}
@Override
public int hashCode() {
int result = name.hashCode();
result = 31 * result + type.hashCode();
result = 31 * result + declClass.hashCode();
return result;
}
@Override
public String toString() {
return declClass + "." + name + " " + type;
@@ -1,9 +1,9 @@
package jadx.dex.info;
package jadx.core.dex.info;
import jadx.codegen.TypeGen;
import jadx.dex.instructions.args.ArgType;
import jadx.dex.nodes.DexNode;
import jadx.utils.Utils;
import jadx.core.codegen.TypeGen;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.DexNode;
import jadx.core.utils.Utils;
import java.util.List;
@@ -49,10 +49,10 @@ public final class MethodInfo {
return declClass.getFullName() + "." + name;
}
public String getFullId() {
return declClass.getFullName() + "." + shortId;
}
/**
* Method name and signature
*/
@@ -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;
@@ -1,4 +1,4 @@
package jadx.dex.instructions;
package jadx.core.dex.instructions;
public enum ArithOp {
ADD("+"),
@@ -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,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 GotoNode extends InsnNode {
@@ -1,18 +1,25 @@
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.LiteralArg;
import jadx.dex.instructions.args.PrimitiveType;
import jadx.utils.InsnUtils;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.LiteralArg;
import jadx.core.dex.instructions.args.PrimitiveType;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.utils.InsnUtils;
import com.android.dx.io.instructions.DecodedInstruction;
import static jadx.core.utils.BlockUtils.getBlockByOffset;
import static jadx.core.utils.BlockUtils.selectOther;
public class IfNode extends GotoNode {
protected boolean zeroCmp;
protected IfOp op;
private BlockNode thenBlock;
private BlockNode elseBlock;
public IfNode(int targ, InsnArg then, InsnArg els) {
super(InsnType.IF, targ);
addArg(then);
@@ -49,9 +56,12 @@ public class IfNode extends GotoNode {
return zeroCmp;
}
public void invertOp(int targ) {
public void invertCondition() {
op = op.invert();
target = targ;
BlockNode tmp = thenBlock;
thenBlock = elseBlock;
elseBlock = tmp;
target = thenBlock.getStartOffset();
}
public void changeCondition(InsnArg arg1, InsnArg arg2, IfOp op) {
@@ -59,19 +69,38 @@ public class IfNode extends GotoNode {
this.zeroCmp = arg2.isLiteral() && ((LiteralArg) arg2).getLiteral() == 0;
setArg(0, arg1);
if (!zeroCmp) {
if (getArgsCount() == 2)
if (getArgsCount() == 2) {
setArg(1, arg2);
else
} else {
addArg(arg2);
}
}
}
public void initBlocks(BlockNode curBlock) {
thenBlock = getBlockByOffset(target, curBlock.getSuccessors());
if (curBlock.getSuccessors().size() == 1) {
elseBlock = thenBlock;
} else {
elseBlock = selectOther(thenBlock, curBlock.getSuccessors());
}
target = thenBlock.getStartOffset();
}
public BlockNode getThenBlock() {
return thenBlock;
}
public BlockNode getElseBlock() {
return elseBlock;
}
@Override
public String toString() {
return InsnUtils.formatOffset(offset) + ": "
+ InsnUtils.insnTypeToString(insnType)
+ getArg(0) + " " + op.getSymbol()
+ " " + (zeroCmp ? "0" : getArg(1))
+ " -> " + InsnUtils.formatOffset(target);
+ " -> " + (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);
@@ -569,19 +571,21 @@ public class InsnDecoder {
private InsnNode decodeSwitch(DecodedInstruction insn, int offset, boolean packed) {
int payloadOffset = insn.getTarget();
DecodedInstruction payload = insnArr[payloadOffset];
int[] keys;
Object[] keys;
int[] targets;
if (packed) {
PackedSwitchPayloadDecodedInstruction ps = (PackedSwitchPayloadDecodedInstruction) payload;
targets = ps.getTargets();
keys = new int[targets.length];
keys = new Object[targets.length];
int k = ps.getFirstKey();
for (int i = 0; i < keys.length; i++)
keys[i] = k++;
} else {
SparseSwitchPayloadDecodedInstruction ss = (SparseSwitchPayloadDecodedInstruction) payload;
targets = ss.getTargets();
keys = ss.getKeys();
keys = new Object[targets.length];
for (int i = 0; i < keys.length; i++)
keys[i] = ss.getKeys()[i];
}
// convert from relative to absolute offsets
for (int i = 0; i < targets.length; i++) {
@@ -593,7 +597,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) {
@@ -657,7 +661,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 +672,27 @@ 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)
InsnNode node = new InsnNode(type, args == null ? 0 : args.length);
node.setResult(res);
if (args != null) {
for (InsnArg arg : args)
inode.addArg(arg);
return inode;
node.addArg(arg);
}
return node;
}
private int getMoveResultRegister(DecodedInstruction[] insnArr, int offset) {
@@ -1,9 +1,11 @@
package jadx.dex.instructions;
package jadx.core.dex.instructions;
public enum InsnType {
NOP, // replacement for removed instructions
CONST,
CONST_STR,
CONST_CLASS,
ARITH,
NEG,
@@ -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;
@@ -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;
}
@@ -1,7 +1,8 @@
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.utils.Utils;
import java.util.ArrayList;
import java.util.Arrays;
@@ -39,10 +40,20 @@ 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);
}
@@ -71,7 +82,7 @@ public abstract class ArgType {
return new UnknownArg(types);
}
private static abstract class KnownTypeArg extends ArgType {
private abstract static class KnownTypeArg extends ArgType {
@Override
public boolean isTypeKnown() {
return true;
@@ -96,6 +107,11 @@ public abstract class ArgType {
return true;
}
@Override
boolean internalEquals(Object obj) {
return type == ((PrimitiveArg) obj).type;
}
@Override
public String toString() {
return type.toString();
@@ -107,7 +123,7 @@ public abstract class ArgType {
public ObjectArg(String obj) {
this.object = Utils.cleanObjectName(obj);
this.hash = obj.hashCode();
this.hash = object.hashCode();
}
@Override
@@ -125,6 +141,11 @@ public abstract class ArgType {
return PrimitiveType.OBJECT;
}
@Override
boolean internalEquals(Object obj) {
return object.equals(((ObjectArg) obj).object);
}
@Override
public String toString() {
return object;
@@ -156,6 +177,12 @@ public abstract class ArgType {
return generics;
}
@Override
boolean internalEquals(Object obj) {
return super.internalEquals(obj)
&& Arrays.equals(generics, ((GenericObjectArg) obj).generics);
}
@Override
public String toString() {
return super.toString() + "<" + Utils.arrayToString(generics) + ">";
@@ -195,6 +222,11 @@ 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() + "[]";
@@ -202,7 +234,7 @@ public abstract class ArgType {
}
private static final class UnknownArg extends ArgType {
private final PrimitiveType possibleTypes[];
private final PrimitiveType[] possibleTypes;
public UnknownArg(PrimitiveType[] types) {
this.possibleTypes = types;
@@ -221,27 +253,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);
}
}
}
@@ -302,77 +343,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) {
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) {
@@ -389,19 +461,20 @@ public abstract class ArgType {
public static ArgType parseSignature(String sign) {
int b = sign.indexOf('<');
if (b == -1)
if (b == -1) {
return parse(sign);
if (sign.charAt(0) == '[')
}
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)
if (generics != null) {
return generic(obj, generics.toArray(new ArgType[generics.size()]));
else
} else {
return object(obj);
}
}
public static List<ArgType> parseSignatureList(String str) {
@@ -420,7 +493,6 @@ public abstract class ArgType {
if (str.equals("*")) {
return Arrays.asList(UNKNOWN);
}
List<ArgType> signs = new ArrayList<ArgType>(3);
int obj = 0;
int objStart = 0;
@@ -452,8 +524,9 @@ public abstract class ArgType {
if (gen == 0) {
obj = 0;
String o = str.substring(objStart, pos);
if (o.length() > 0)
if (o.length() > 0) {
type = genericType(o);
}
}
break;
@@ -511,8 +584,9 @@ public abstract class ArgType {
}
prev = arg;
} else {
if (!arg.getObject().equals(Consts.CLASS_OBJECT))
if (!arg.getObject().equals(Consts.CLASS_OBJECT)) {
genList.add(arg);
}
}
}
if (prev != null) {
@@ -553,8 +627,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 +649,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;
}
}
@@ -1,7 +1,7 @@
package jadx.dex.instructions.args;
package jadx.core.dex.instructions.args;
import jadx.dex.nodes.InsnNode;
import jadx.utils.InsnUtils;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.utils.InsnUtils;
import com.android.dx.io.instructions.DecodedInstruction;
@@ -14,7 +14,6 @@ public abstract class InsnArg extends Typed {
protected InsnNode parentInsn;
public static RegisterArg reg(int regNum, ArgType type) {
assert regNum >= 0 : "Register number must be positive";
return new RegisterArg(regNum, type);
}
@@ -22,6 +21,12 @@ public abstract class InsnArg extends Typed {
return reg(InsnUtils.getArg(insn, argNum), type);
}
public static RegisterArg immutableReg(int regNum, ArgType type) {
RegisterArg r = new RegisterArg(regNum);
r.forceSetTypedVar(new ImmutableTypedVar(type));
return r;
}
public static LiteralArg lit(long literal, ArgType type) {
return new LiteralArg(literal, type);
}
@@ -30,7 +35,7 @@ public abstract class InsnArg extends Typed {
return lit(insn.getLiteral(), type);
}
public static InsnWrapArg wrap(InsnNode insn) {
private static InsnWrapArg wrap(InsnNode insn) {
return new InsnWrapArg(insn);
}
@@ -46,6 +51,14 @@ public abstract class InsnArg extends Typed {
return false;
}
public boolean isNamed() {
return false;
}
public boolean isField() {
return false;
}
public InsnNode getParentInsn() {
return parentInsn;
}
@@ -54,26 +67,44 @@ public abstract class InsnArg extends Typed {
this.parentInsn = parentInsn;
}
public InsnWrapArg wrapInstruction(InsnNode insn) {
assert parentInsn != insn : "Can't wrap instruction info itself";
int count = parentInsn.getArgsCount();
public InsnArg wrapInstruction(InsnNode insn) {
InsnNode parent = parentInsn;
assert parent != insn : "Can't wrap instruction info itself";
int count = parent.getArgsCount();
for (int i = 0; i < count; i++) {
if (parentInsn.getArg(i) == this) {
InsnWrapArg arg = wrap(insn);
parentInsn.setArg(i, arg);
if (parent.getArg(i) == this) {
InsnArg arg = wrapArg(insn);
parent.setArg(i, arg);
return arg;
}
}
return null;
}
public static InsnArg wrapArg(InsnNode insn) {
InsnArg arg;
switch (insn.getType()) {
case MOVE:
case CONST:
arg = insn.getArg(0);
break;
case CONST_STR:
arg = wrap(insn);
arg.getTypedVar().forceSetType(ArgType.STRING);
break;
case CONST_CLASS:
arg = wrap(insn);
arg.getTypedVar().forceSetType(ArgType.CLASS);
break;
default:
arg = wrap(insn);
break;
}
return arg;
}
public boolean isThis() {
// must be implemented in RegisterArg
return false;
}
public int getRegNum() {
throw new UnsupportedOperationException("Must be called from RegisterArg");
}
}
@@ -1,8 +1,8 @@
package jadx.dex.instructions.args;
package jadx.core.dex.instructions.args;
import jadx.dex.nodes.InsnNode;
import jadx.core.dex.nodes.InsnNode;
public class InsnWrapArg extends InsnArg {
public final class InsnWrapArg extends InsnArg {
private final InsnNode wrappedInsn;
@@ -1,17 +1,27 @@
package jadx.dex.instructions.args;
package jadx.core.dex.instructions.args;
import jadx.codegen.TypeGen;
import jadx.utils.exceptions.JadxRuntimeException;
import jadx.core.codegen.TypeGen;
import jadx.core.utils.exceptions.JadxRuntimeException;
public class LiteralArg extends InsnArg {
public final class LiteralArg extends InsnArg {
private final long literal;
public LiteralArg(long value, ArgType type) {
if (value != 0) {
if (type.isObject()) {
throw new JadxRuntimeException("Wrong literal type: " + type + " for value: " + value);
} else if (!type.isTypeKnown()
&& !type.contains(PrimitiveType.LONG)
&& !type.contains(PrimitiveType.DOUBLE)) {
ArgType m = ArgType.merge(type, ArgType.NARROW_NUMBERS);
if (m != null) {
type = m;
}
}
}
this.literal = value;
this.typedVar = new TypedVar(type);
if (literal != 0 && type.isObject())
throw new RuntimeException("wrong literal type");
}
public long getLiteral() {
@@ -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"),
@@ -1,11 +1,22 @@
package jadx.dex.instructions.args;
package jadx.core.dex.instructions.args;
import jadx.dex.instructions.IndexInsnNode;
import jadx.dex.instructions.InsnType;
import jadx.dex.nodes.InsnNode;
import jadx.dex.visitors.InstructionRemover;
import jadx.core.dex.attributes.AttributeType;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.instructions.ConstClassNode;
import jadx.core.dex.instructions.ConstStringNode;
import jadx.core.dex.instructions.IndexInsnNode;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.nodes.DexNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.parser.FieldValueAttr;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class RegisterArg extends InsnArg {
private static final Logger LOG = LoggerFactory.getLogger(RegisterArg.class);
protected final int regNum;
public RegisterArg(int rn) {
@@ -17,7 +28,6 @@ public class RegisterArg extends InsnArg {
this.regNum = rn;
}
@Override
public int getRegNum() {
return regNum;
}
@@ -42,17 +52,32 @@ public class RegisterArg extends InsnArg {
/**
* Return constant value from register assign or null if not constant
*
*
* @return LiteralArg, String or ArgType
*/
public Object getConstValue() {
public Object getConstValue(DexNode dex) {
InsnNode parInsn = getAssignInsn();
if (parInsn != null && parInsn.getType() == InsnType.CONST) {
if (parInsn.getArgsCount() == 0) {
// const in 'index' - string or class
return ((IndexInsnNode) parInsn).getIndex();
} else {
return parInsn.getArg(0);
if (parInsn != null) {
InsnType insnType = parInsn.getType();
switch (insnType) {
case CONST:
return parInsn.getArg(0);
case CONST_STR:
return ((ConstStringNode) parInsn).getString();
case CONST_CLASS:
return ((ConstClassNode) parInsn).getClsType();
case SGET:
FieldInfo f = (FieldInfo) ((IndexInsnNode) parInsn).getIndex();
FieldNode fieldNode = dex.resolveField(f);
if (fieldNode != null) {
FieldValueAttr attr = (FieldValueAttr) fieldNode.getAttributes().get(AttributeType.FIELD_VALUE);
if (attr != null) {
return attr.getValue();
}
} else {
LOG.warn("Field {} not found in dex {}", f, dex);
}
break;
}
}
return null;
@@ -62,17 +87,14 @@ public class RegisterArg extends InsnArg {
public boolean isThis() {
if (isRegister()) {
String name = getTypedVar().getName();
if (name != null && name.equals("this"))
if (name != null && name.equals("this")) {
return true;
}
// maybe it was moved from 'this' register
InsnNode ai = getAssignInsn();
if (ai != null && ai.getType() == InsnType.MOVE) {
if (ai.getArg(0).isThis()) {
// actually we need to remove this instruction but we can't
// because of iterating on instructions list
// so unbind insn and rely on code shrinker
InstructionRemover.unbindInsn(ai);
InsnArg arg = ai.getArg(0);
if (arg != this && arg.isThis()) {
return true;
}
}
@@ -0,0 +1,71 @@
package jadx.core.dex.instructions.args;
import java.util.List;
public abstract class Typed {
TypedVar typedVar;
public TypedVar getTypedVar() {
return typedVar;
}
public ArgType getType() {
return typedVar.getType();
}
public boolean merge(Typed var) {
return typedVar.merge(var.getTypedVar());
}
public boolean merge(ArgType var) {
return typedVar.merge(var);
}
public void forceSetTypedVar(TypedVar arg) {
this.typedVar = arg;
}
public void mergeDebugInfo(Typed arg) {
merge(arg);
mergeName(arg);
}
protected void mergeName(Typed arg) {
getTypedVar().mergeName(arg.getTypedVar());
}
public boolean replaceTypedVar(Typed var) {
TypedVar curVar = this.typedVar;
TypedVar newVar = var.typedVar;
if (curVar == newVar) {
return false;
}
if (curVar != null) {
if (curVar.isImmutable()) {
moveInternals(newVar, curVar);
} else {
newVar.merge(curVar);
moveInternals(curVar, newVar);
this.typedVar = newVar;
}
} else {
this.typedVar = newVar;
}
return true;
}
private void moveInternals(TypedVar from, TypedVar to) {
List<InsnArg> curUseList = from.getUseList();
if (curUseList.size() != 0) {
for (InsnArg arg : curUseList) {
if (arg != this) {
arg.forceSetTypedVar(to);
}
}
to.getUseList().addAll(curUseList);
curUseList.clear();
}
to.mergeName(from);
}
}
@@ -1,4 +1,4 @@
package jadx.dex.instructions.args;
package jadx.core.dex.instructions.args;
import java.util.ArrayList;
import java.util.List;
@@ -20,13 +20,8 @@ public class TypedVar {
/**
* This method must be used very carefully
*/
public boolean forceSetType(ArgType newType) {
if (newType != null && !type.equals(newType)) {
type = newType;
return true;
} else {
return false;
}
public void forceSetType(ArgType newType) {
type = newType;
}
public boolean merge(TypedVar typedVar) {
@@ -55,6 +50,19 @@ public class TypedVar {
this.name = name;
}
public void mergeName(TypedVar arg) {
String name = arg.getName();
if (name != null) {
setName(name);
} else if (getName() != null) {
arg.setName(getName());
}
}
public boolean isImmutable() {
return false;
}
@Override
public int hashCode() {
return type.hashCode() * 31 + (name == null ? 0 : name.hashCode());
@@ -64,7 +72,7 @@ public class TypedVar {
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
if (!(obj instanceof TypedVar)) return false;
TypedVar other = (TypedVar) obj;
if (!type.equals(other.type)) return false;
if (name == null) {
@@ -78,7 +86,7 @@ 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();
}
@@ -1,10 +1,10 @@
package jadx.dex.nodes;
package jadx.core.dex.nodes;
import jadx.dex.attributes.AttrNode;
import jadx.dex.attributes.AttributeType;
import jadx.dex.attributes.BlockRegState;
import jadx.dex.attributes.LoopAttr;
import jadx.utils.InsnUtils;
import jadx.core.dex.attributes.AttrNode;
import jadx.core.dex.attributes.AttributeType;
import jadx.core.dex.attributes.BlockRegState;
import jadx.core.dex.attributes.LoopAttr;
import jadx.core.utils.InsnUtils;
import java.util.ArrayList;
import java.util.BitSet;
@@ -1,21 +1,24 @@
package jadx.dex.nodes;
package jadx.core.dex.nodes;
import jadx.Consts;
import jadx.dex.attributes.AttrNode;
import jadx.dex.attributes.AttributeType;
import jadx.dex.attributes.SourceFileAttr;
import jadx.dex.attributes.annotations.Annotation;
import jadx.dex.info.AccessInfo;
import jadx.dex.info.AccessInfo.AFType;
import jadx.dex.info.ClassInfo;
import jadx.dex.info.FieldInfo;
import jadx.dex.info.MethodInfo;
import jadx.dex.instructions.args.ArgType;
import jadx.dex.nodes.parser.AnnotationsParser;
import jadx.dex.nodes.parser.FieldValueAttr;
import jadx.dex.nodes.parser.StaticValuesParser;
import jadx.utils.Utils;
import jadx.utils.exceptions.DecodeException;
import jadx.core.Consts;
import jadx.core.codegen.CodeWriter;
import jadx.core.dex.attributes.AttributeType;
import jadx.core.dex.attributes.LineAttrNode;
import jadx.core.dex.attributes.SourceFileAttr;
import jadx.core.dex.attributes.annotations.Annotation;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.info.AccessInfo.AFType;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.LiteralArg;
import jadx.core.dex.instructions.args.PrimitiveType;
import jadx.core.dex.nodes.parser.AnnotationsParser;
import jadx.core.dex.nodes.parser.FieldValueAttr;
import jadx.core.dex.nodes.parser.StaticValuesParser;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.DecodeException;
import java.util.ArrayList;
import java.util.Collections;
@@ -31,7 +34,7 @@ import com.android.dx.io.ClassData.Field;
import com.android.dx.io.ClassData.Method;
import com.android.dx.io.ClassDef;
public class ClassNode extends AttrNode implements ILoadable {
public class ClassNode extends LineAttrNode implements ILoadable {
private static final Logger LOG = LoggerFactory.getLogger(ClassNode.class);
private final DexNode dex;
@@ -48,6 +51,8 @@ public class ClassNode extends AttrNode implements ILoadable {
private final Map<Object, FieldNode> constFields = new HashMap<Object, FieldNode>();
private CodeWriter code; // generated code
public ClassNode(DexNode dex, ClassDef cls) throws DecodeException {
this.dex = dex;
this.clsInfo = ClassInfo.fromDex(dex, cls.getTypeIndex());
@@ -61,9 +66,7 @@ public class ClassNode extends AttrNode implements ILoadable {
this.interfaces.add(ClassInfo.fromDex(dex, interfaceIdx));
}
if (cls.getClassDataOffset() == 0) {
// nothing to load
} else {
if (cls.getClassDataOffset() != 0) {
ClassData clsData = dex.readClassData(cls);
for (Method mth : clsData.getDirectMethods())
@@ -87,9 +90,9 @@ public class ClassNode extends AttrNode implements ILoadable {
setFieldsTypesFromSignature();
int sfIdx = cls.getSourceFileIndex();
if(sfIdx != DexNode.NO_INDEX) {
if (sfIdx != DexNode.NO_INDEX) {
String fileName = dex.getString(sfIdx);
if(!this.getFullName().contains(fileName.replace(".java", ""))) {
if (!this.getFullName().contains(fileName.replace(".java", ""))) {
this.getAttributes().add(new SourceFileAttr(fileName));
LOG.debug("Class '{}' compiled from '{}'", this, fileName);
}
@@ -134,9 +137,13 @@ public class ClassNode extends AttrNode implements ILoadable {
parser.processFields(staticFields);
for (FieldNode f : staticFields) {
if (f.getType().equals(ArgType.STRING)) {
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);
}
}
@@ -231,6 +238,53 @@ public class ClassNode extends AttrNode implements ILoadable {
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) {
@@ -279,26 +333,32 @@ public class ClassNode extends AttrNode implements ILoadable {
innerClasses.add(cls);
}
public boolean isEnum() {
return getAccessFlags().isEnum() && getSuperClass().getFullName().equals(Consts.CLASS_ENUM);
}
public boolean isAnonymous() {
boolean simple = false;
for (MethodNode m : methods) {
MethodInfo mi = m.getMethodInfo();
if (mi.isConstructor() && mi.getArgumentsTypes().size() == 0) {
simple = true;
break;
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 simple && Character.isDigit(getShortName().charAt(0));
return null;
}
public AccessInfo getAccessFlags() {
return accessFlags;
}
public Map<Object, FieldNode> getConstFields() {
return constFields;
}
public DexNode dex() {
return dex;
}
@@ -319,9 +379,20 @@ public class ClassNode extends AttrNode implements ILoadable {
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,61 @@
package jadx.core.dex.nodes;
import jadx.core.dex.attributes.LineAttrNode;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.info.AccessInfo.AFType;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.instructions.args.ArgType;
import com.android.dx.io.ClassData.Field;
public class FieldNode extends LineAttrNode {
private final FieldInfo fieldInfo;
private final AccessInfo accFlags;
private ArgType type; // store signature
public FieldNode(ClassNode cls, Field field) {
this.fieldInfo = FieldInfo.fromDex(cls.dex(), field.getFieldIndex());
this.type = fieldInfo.getType();
this.accFlags = new AccessInfo(field.getAccessFlags(), AFType.FIELD);
}
public FieldInfo getFieldInfo() {
return fieldInfo;
}
public AccessInfo getAccessFlags() {
return accFlags;
}
public String getName() {
return fieldInfo.getName();
}
public ArgType getType() {
return type;
}
public void setType(ArgType type) {
this.type = type;
}
@Override
public int hashCode() {
return fieldInfo.hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
FieldNode other = (FieldNode) obj;
return fieldInfo.equals(other.fieldInfo);
}
@Override
public String toString() {
return fieldInfo.getDeclClass() + "." + fieldInfo.getName() + " " + type;
}
}
@@ -1,8 +1,8 @@
package jadx.dex.nodes;
package jadx.core.dex.nodes;
import java.util.List;
public interface IBlock extends IContainer {
public List<InsnNode> getInstructions();
List<InsnNode> getInstructions();
}
@@ -0,0 +1,6 @@
package jadx.core.dex.nodes;
import jadx.core.dex.attributes.IAttributeNode;
public interface IContainer extends IAttributeNode {
}
@@ -0,0 +1,19 @@
package jadx.core.dex.nodes;
import jadx.core.utils.exceptions.DecodeException;
public interface ILoadable {
/**
* On demand loading
*
* @throws DecodeException
*/
void load() throws DecodeException;
/**
* Free resources
*/
void unload();
}
@@ -0,0 +1,11 @@
package jadx.core.dex.nodes;
import java.util.List;
public interface IRegion extends IContainer {
IRegion getParent();
List<IContainer> getSubBlocks();
}
@@ -1,6 +1,6 @@
package jadx.dex.nodes;
package jadx.core.dex.nodes;
import jadx.dex.attributes.AttrNode;
import jadx.core.dex.attributes.AttrNode;
import java.util.List;
@@ -8,7 +8,7 @@ public class InsnContainer extends AttrNode implements IBlock {
private List<InsnNode> insns;
public void setInstructions(List<InsnNode> insns) {
public InsnContainer(List<InsnNode> insns) {
this.insns = insns;
}
@@ -1,13 +1,13 @@
package jadx.dex.nodes;
package jadx.core.dex.nodes;
import jadx.dex.attributes.AttrNode;
import jadx.dex.instructions.InsnType;
import jadx.dex.instructions.args.ArgType;
import jadx.dex.instructions.args.InsnArg;
import jadx.dex.instructions.args.InsnWrapArg;
import jadx.dex.instructions.args.RegisterArg;
import jadx.utils.InsnUtils;
import jadx.utils.Utils;
import jadx.core.dex.attributes.LineAttrNode;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.InsnWrapArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.utils.InsnUtils;
import jadx.core.utils.Utils;
import java.util.ArrayList;
import java.util.Collections;
@@ -15,7 +15,7 @@ import java.util.List;
import com.android.dx.io.instructions.DecodedInstruction;
public class InsnNode extends AttrNode {
public class InsnNode extends LineAttrNode {
protected final InsnType insnType;
@@ -71,7 +71,7 @@ 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;
@@ -125,13 +125,38 @@ public class InsnNode extends AttrNode {
public void getRegisterArgs(List<RegisterArg> list) {
for (InsnArg arg : this.getArguments()) {
if (arg.isRegister())
if (arg.isRegister()) {
list.add((RegisterArg) arg);
else if (arg.isInsnWrap())
} else if (arg.isInsnWrap()) {
((InsnWrapArg) arg).getWrapInsn().getRegisterArgs(list);
}
}
}
public boolean canReorder() {
switch (getType()) {
case CONST:
case CONST_STR:
case CONST_CLASS:
case CAST:
case MOVE:
case ARITH:
case NEG:
case CMP_L:
case CMP_G:
case CHECK_CAST:
case INSTANCE_OF:
case FILL_ARRAY:
case FILLED_NEW_ARRAY:
case NEW_ARRAY:
case NEW_MULTIDIM_ARRAY:
case STR_CONCAT:
case MOVE_EXCEPTION:
return true;
}
return false;
}
@Override
public String toString() {
return InsnUtils.formatOffset(offset) + ": "
@@ -1,28 +1,28 @@
package jadx.dex.nodes;
package jadx.core.dex.nodes;
import jadx.Consts;
import jadx.dex.attributes.AttrNode;
import jadx.dex.attributes.AttributeFlag;
import jadx.dex.attributes.JumpAttribute;
import jadx.dex.attributes.annotations.Annotation;
import jadx.dex.info.AccessInfo;
import jadx.dex.info.AccessInfo.AFType;
import jadx.dex.info.ClassInfo;
import jadx.dex.info.MethodInfo;
import jadx.dex.instructions.GotoNode;
import jadx.dex.instructions.IfNode;
import jadx.dex.instructions.InsnDecoder;
import jadx.dex.instructions.SwitchNode;
import jadx.dex.instructions.args.ArgType;
import jadx.dex.instructions.args.InsnArg;
import jadx.dex.instructions.args.RegisterArg;
import jadx.dex.instructions.mods.ConstructorInsn;
import jadx.dex.nodes.parser.DebugInfoParser;
import jadx.dex.trycatch.ExcHandlerAttr;
import jadx.dex.trycatch.ExceptionHandler;
import jadx.dex.trycatch.TryCatchBlock;
import jadx.utils.Utils;
import jadx.utils.exceptions.DecodeException;
import jadx.core.Consts;
import jadx.core.dex.attributes.AttributeFlag;
import jadx.core.dex.attributes.JumpAttribute;
import jadx.core.dex.attributes.LineAttrNode;
import jadx.core.dex.attributes.LoopAttr;
import jadx.core.dex.attributes.annotations.Annotation;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.info.AccessInfo.AFType;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.GotoNode;
import jadx.core.dex.instructions.IfNode;
import jadx.core.dex.instructions.InsnDecoder;
import jadx.core.dex.instructions.SwitchNode;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.parser.DebugInfoParser;
import jadx.core.dex.trycatch.ExcHandlerAttr;
import jadx.core.dex.trycatch.ExceptionHandler;
import jadx.core.dex.trycatch.TryCatchBlock;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.DecodeException;
import java.util.ArrayList;
import java.util.Collections;
@@ -39,7 +39,7 @@ import com.android.dx.io.Code;
import com.android.dx.io.Code.CatchHandler;
import com.android.dx.io.Code.Try;
public class MethodNode extends AttrNode implements ILoadable {
public class MethodNode extends LineAttrNode implements ILoadable {
private static final Logger LOG = LoggerFactory.getLogger(MethodNode.class);
private final MethodInfo mthInfo;
@@ -60,10 +60,9 @@ public class MethodNode extends AttrNode implements ILoadable {
private BlockNode enterBlock;
private List<BlockNode> exitBlocks;
private ConstructorInsn superCall;
private IContainer region;
private List<ExceptionHandler> exceptionHandlers;
private List<LoopAttr> loops = Collections.emptyList();
public MethodNode(ClassNode classNode, Method mthData) {
this.mthInfo = MethodInfo.fromDex(classNode.dex(), mthData.getMethodIndex());
@@ -99,8 +98,17 @@ public class MethodNode extends AttrNode implements ILoadable {
initTryCatches(mthCode, insnByOffset);
initJumps(insnByOffset);
if (mthCode.getDebugInfoOffset() > 0) {
(new DebugInfoParser(this, mthCode.getDebugInfoOffset(), insnByOffset)).process();
int debugInfoOffset = mthCode.getDebugInfoOffset();
if (debugInfoOffset > 0) {
DebugInfoParser debugInfoParser = new DebugInfoParser(this, debugInfoOffset, insnByOffset);
debugInfoParser.process();
if (instructions.size() != 0) {
int line = instructions.get(0).getSourceLine();
if (line != 0) {
this.setSourceLine(line - 1);
}
}
}
} catch (Exception e) {
throw new DecodeException(this, "Load method exception", e);
@@ -158,23 +166,24 @@ public class MethodNode extends AttrNode implements ILoadable {
if (argsTypes == null)
return false;
if (argsTypes.size() != mthInfo.getArgumentsTypes().size()) {
List<ArgType> mthArgs = mthInfo.getArgumentsTypes();
if (argsTypes.size() != mthArgs.size()) {
if (argsTypes.isEmpty()) {
return false;
}
if (!mthInfo.isConstructor()) {
LOG.warn("Wrong signature parse result: " + sign + " -> " + argsTypes
+ ", not generic version: " + mthInfo.getArgumentsTypes());
+ ", not generic version: " + mthArgs);
return false;
} else if (getParentClass().getAccessFlags().isEnum()) {
// TODO:
argsTypes.add(0, mthInfo.getArgumentsTypes().get(0));
argsTypes.add(1, mthInfo.getArgumentsTypes().get(1));
argsTypes.add(0, mthArgs.get(0));
argsTypes.add(1, mthArgs.get(1));
} else {
// add synthetic arg for outer class
argsTypes.add(0, mthInfo.getArgumentsTypes().get(0));
argsTypes.add(0, mthArgs.get(0));
}
if (argsTypes.size() != mthInfo.getArgumentsTypes().size()) {
if (argsTypes.size() != mthArgs.size()) {
return false;
}
}
@@ -192,22 +201,19 @@ public class MethodNode extends AttrNode implements ILoadable {
for (ArgType arg : args)
pos -= arg.getRegCount();
}
if (accFlags.isStatic()) {
thisArg = null;
} else {
thisArg = InsnArg.reg(pos - 1, parentClass.getClassInfo().getType());
thisArg = InsnArg.immutableReg(pos - 1, parentClass.getClassInfo().getType());
thisArg.getTypedVar().setName("this");
}
if (args.isEmpty()) {
argsList = Collections.emptyList();
return;
}
argsList = new ArrayList<RegisterArg>(args.size());
for (ArgType arg : args) {
argsList.add(InsnArg.reg(pos, arg));
argsList.add(InsnArg.immutableReg(pos, arg));
pos += arg.getRegCount();
}
}
@@ -223,6 +229,11 @@ public class MethodNode extends AttrNode implements ILoadable {
}
}
public RegisterArg removeFirstArgument() {
this.getAttributes().add(AttributeFlag.SKIP_FIRST_ARG);
return argsList.remove(0);
}
public RegisterArg getThisArg() {
return thisArg;
}
@@ -244,8 +255,8 @@ public class MethodNode extends AttrNode implements ILoadable {
// and we don't need this mapping anymore,
// but in maven repository still old version
Set<Integer> handlerSet = new HashSet<Integer>(tries.length);
for (Try try_ : tries) {
handlerSet.add(try_.getHandlerOffset());
for (Try aTry : tries) {
handlerSet.add(aTry.getHandlerOffset());
}
List<Integer> handlerList = new ArrayList<Integer>(catchBlocks.length);
handlerList.addAll(handlerSet);
@@ -257,17 +268,17 @@ public class MethodNode extends AttrNode implements ILoadable {
Set<Integer> addrs = new HashSet<Integer>();
List<TryCatchBlock> catches = new ArrayList<TryCatchBlock>(catchBlocks.length);
for (CatchHandler catch_ : catchBlocks) {
for (CatchHandler handler : catchBlocks) {
TryCatchBlock tcBlock = new TryCatchBlock();
catches.add(tcBlock);
for (int i = 0; i < catch_.getAddresses().length; i++) {
int addr = catch_.getAddresses()[i];
ClassInfo type = ClassInfo.fromDex(parentClass.dex(), catch_.getTypeIndexes()[i]);
for (int i = 0; i < handler.getAddresses().length; i++) {
int addr = handler.getAddresses()[i];
ClassInfo type = ClassInfo.fromDex(parentClass.dex(), handler.getTypeIndexes()[i]);
tcBlock.addHandler(this, addr, type);
addrs.add(addr);
hc++;
}
int addr = catch_.getCatchAllAddress();
int addr = handler.getCatchAllAddress();
if (addr >= 0) {
tcBlock.addHandler(this, addr, null);
addrs.add(addr);
@@ -298,11 +309,11 @@ public class MethodNode extends AttrNode implements ILoadable {
}
// attach TRY_ENTER, TRY_LEAVE attributes to instructions
for (Try try_ : tries) {
int catchNum = handlerList.indexOf(try_.getHandlerOffset());
for (Try aTry : tries) {
int catchNum = handlerList.indexOf(aTry.getHandlerOffset());
TryCatchBlock block = catches.get(catchNum);
int offset = try_.getStartAddress();
int end = offset + try_.getInstructionCount() - 1;
int offset = aTry.getStartAddress();
int end = offset + aTry.getInstructionCount() - 1;
insnByOffset[offset].getAttributes().add(AttributeFlag.TRY_ENTER);
while (offset <= end && offset >= 0) {
@@ -411,6 +422,25 @@ public class MethodNode extends AttrNode implements ILoadable {
this.exitBlocks.add(exitBlock);
}
public void registerLoop(LoopAttr loop) {
if (loops.isEmpty()) {
loops = new ArrayList<LoopAttr>(5);
}
loops.add(loop);
}
public LoopAttr getLoopForBlock(BlockNode block) {
for (LoopAttr loop : loops) {
if (loop.getLoopBlocks().contains(block))
return loop;
}
return null;
}
public int getLoopsCount() {
return loops.size();
}
public ExceptionHandler addExceptionHandler(ExceptionHandler handler) {
if (exceptionHandlers == null) {
exceptionHandlers = new ArrayList<ExceptionHandler>(2);
@@ -428,6 +458,26 @@ public class MethodNode extends AttrNode implements ILoadable {
return exceptionHandlers;
}
/**
* Return true if exists method with same name and arguments count
*/
public boolean isArgsOverload() {
int argsCount = mthInfo.getArgumentsTypes().size();
if (argsCount == 0) {
return false;
}
String name = getName();
List<MethodNode> methods = parentClass.getMethods();
for (MethodNode method : methods) {
if (this != method
&& method.getName().equals(name)
&& method.mthInfo.getArgumentsTypes().size() == argsCount)
return true;
}
return false;
}
public int getRegsCount() {
return regsCount;
}
@@ -436,14 +486,6 @@ public class MethodNode extends AttrNode implements ILoadable {
return accFlags;
}
public void setSuperCall(ConstructorInsn insn) {
this.superCall = insn;
}
public ConstructorInsn getSuperCall() {
return this.superCall;
}
public IContainer getRegion() {
return region;
}
@@ -460,11 +502,23 @@ public class MethodNode extends AttrNode implements ILoadable {
return mthInfo;
}
@Override
public int hashCode() {
return mthInfo.hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
MethodNode other = (MethodNode) obj;
return mthInfo.equals(other.mthInfo);
}
@Override
public String toString() {
return retType
+ " " + parentClass.getFullName() + "." + mthInfo.getName()
+ "(" + Utils.listToString(mthInfo.getArgumentsTypes()) + ")";
}
}
@@ -0,0 +1,100 @@
package jadx.core.dex.nodes;
import jadx.core.clsp.ClspGraph;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.files.InputFile;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class RootNode {
private final Map<String, ClassNode> names = new HashMap<String, ClassNode>();
private List<DexNode> dexNodes;
public void load(List<InputFile> dexFiles) throws DecodeException {
dexNodes = new ArrayList<DexNode>(dexFiles.size());
for (InputFile dex : dexFiles) {
DexNode dexNode;
try {
dexNode = new DexNode(this, dex);
} catch (Exception e) {
throw new DecodeException("Error decode file: " + dex, e);
}
dexNodes.add(dexNode);
}
for (DexNode dexNode : dexNodes) {
dexNode.loadClasses();
}
List<ClassNode> classes = new ArrayList<ClassNode>();
for (DexNode dexNode : dexNodes) {
for (ClassNode cls : dexNode.getClasses()) {
names.put(cls.getFullName(), cls);
}
classes.addAll(dexNode.getClasses());
}
try {
initClassPath(classes);
} catch (IOException e) {
throw new DecodeException("Error loading classpath", e);
}
initInnerClasses(classes);
}
private void initClassPath(List<ClassNode> classes) throws IOException, DecodeException {
ClspGraph clsp = new ClspGraph();
clsp.load();
clsp.addApp(classes);
ArgType.setClsp(clsp);
}
private void initInnerClasses(List<ClassNode> classes) {
// move inner classes
List<ClassNode> inner = new ArrayList<ClassNode>();
for (ClassNode cls : classes) {
if (cls.getClassInfo().isInner())
inner.add(cls);
}
for (ClassNode cls : inner) {
ClassNode parent = resolveClass(cls.getClassInfo().getParentClass());
if (parent == null) {
names.remove(cls.getFullName());
cls.getClassInfo().notInner();
names.put(cls.getFullName(), cls);
} else {
parent.addInnerClass(cls);
}
}
}
public List<ClassNode> getClasses(boolean includeInner) {
List<ClassNode> classes = new ArrayList<ClassNode>();
for (DexNode dexNode : dexNodes) {
for (ClassNode cls : dexNode.getClasses()) {
if (includeInner) {
classes.add(cls);
} else {
if (!cls.getClassInfo().isInner())
classes.add(cls);
}
}
}
return classes;
}
public ClassNode searchClassByName(String fullName) {
return names.get(fullName);
}
public ClassNode resolveClass(ClassInfo cls) {
String fullName = cls.getFullName();
return searchClassByName(fullName);
}
}
@@ -1,17 +1,17 @@
package jadx.dex.nodes.parser;
package jadx.core.dex.nodes.parser;
import jadx.dex.attributes.annotations.Annotation;
import jadx.dex.attributes.annotations.Annotation.Visibility;
import jadx.dex.attributes.annotations.AnnotationsList;
import jadx.dex.attributes.annotations.MethodParameters;
import jadx.dex.nodes.ClassNode;
import jadx.dex.nodes.DexNode;
import jadx.dex.nodes.FieldNode;
import jadx.dex.nodes.MethodNode;
import jadx.utils.exceptions.DecodeException;
import jadx.core.dex.attributes.annotations.Annotation;
import jadx.core.dex.attributes.annotations.Annotation.Visibility;
import jadx.core.dex.attributes.annotations.AnnotationsList;
import jadx.core.dex.attributes.annotations.MethodParameters;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.DexNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.exceptions.DecodeException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -26,27 +26,26 @@ public class AnnotationsParser {
Section section = dex.openSection(offset);
// TODO read as unsigned int
int class_annotations_off = section.readInt();
int fields_size = section.readInt();
int annotated_methods_size = section.readInt();
int annotated_parameters_size = section.readInt();
int classAnnotationsOffset = section.readInt();
int fieldsCount = section.readInt();
int annotatedMethodsCount = section.readInt();
int annotatedParametersCount = section.readInt();
if (class_annotations_off != 0) {
cls.getAttributes().add(readAnnotationSet(class_annotations_off));
if (classAnnotationsOffset != 0) {
cls.getAttributes().add(readAnnotationSet(classAnnotationsOffset));
}
for (int i = 0; i < fields_size; i++) {
for (int i = 0; i < fieldsCount; i++) {
FieldNode f = cls.searchFieldById(section.readInt());
f.getAttributes().add(readAnnotationSet(section.readInt()));
}
for (int i = 0; i < annotated_methods_size; i++) {
for (int i = 0; i < annotatedMethodsCount; i++) {
MethodNode m = cls.searchMethodById(section.readInt());
m.getAttributes().add(readAnnotationSet(section.readInt()));
// LOG.info(m + " " + m.getAttributes());
}
for (int i = 0; i < annotated_parameters_size; i++) {
for (int i = 0; i < annotatedParametersCount; i++) {
MethodNode mth = cls.searchMethodById(section.readInt());
// read annotation ref list
Section ss = dex.openSection(section.readInt());
@@ -73,24 +72,24 @@ public class AnnotationsParser {
return new AnnotationsList(list);
}
private static final Annotation.Visibility[] visibilities = new Annotation.Visibility[] {
private static final Annotation.Visibility[] VISIBILITIES = new Annotation.Visibility[]{
Annotation.Visibility.BUILD,
Annotation.Visibility.RUNTIME,
Annotation.Visibility.SYSTEM
};
public static Annotation readAnnotation(DexNode dex, Section s, boolean readVisibility) throws DecodeException {
EncValueParser ep = new EncValueParser(dex, s);
EncValueParser parser = new EncValueParser(dex, s);
Visibility visibility = null;
if (readVisibility)
visibility = visibilities[s.readByte()];
if (readVisibility) {
visibility = VISIBILITIES[s.readByte()];
}
int typeIndex = s.readUleb128();
int size = s.readUleb128();
Map<String, Object> values = new HashMap<String, Object>(size);
Map<String, Object> values = new LinkedHashMap<String, Object>(size);
for (int i = 0; i < size; i++) {
String name = dex.getString(s.readUleb128());
values.put(name, ep.parseValue());
values.put(name, parser.parseValue());
}
return new Annotation(visibility, dex.getType(typeIndex), values);
}
@@ -1,13 +1,12 @@
package jadx.dex.nodes.parser;
package jadx.core.dex.nodes.parser;
import jadx.dex.attributes.SourceFileAttr;
import jadx.dex.info.LocalVarInfo;
import jadx.dex.instructions.args.InsnArg;
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.attributes.SourceFileAttr;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.DexNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.exceptions.DecodeException;
import java.util.List;
@@ -34,7 +33,7 @@ public class DebugInfoParser {
private final Section section;
private final DexNode dex;
private final LocalVarInfo[] locals;
private final LocalVar[] locals;
private final InsnArg[] activeRegisters;
private final InsnNode[] insnByOffset;
@@ -43,21 +42,20 @@ public class DebugInfoParser {
this.dex = mth.dex();
this.section = dex.openSection(debugOffset);
this.locals = new LocalVarInfo[mth.getRegsCount()];
this.locals = new LocalVar[mth.getRegsCount()];
this.activeRegisters = new InsnArg[mth.getRegsCount()];
this.insnByOffset = insnByOffset;
}
public void process() throws DecodeException {
int addr = 0;
int line;
int line = section.readUleb128();
line = section.readUleb128();
int param_size = section.readUleb128(); // exclude 'this'
int paramsCount = section.readUleb128(); // exclude 'this'
List<RegisterArg> mthArgs = mth.getArguments(false);
assert param_size == mthArgs.size();
assert paramsCount == mthArgs.size();
for (int i = 0; i < param_size; i++) {
for (int i = 0; i < paramsCount; i++) {
int id = section.readUleb128() - 1;
if (id != DexNode.NO_INDEX) {
String name = dex.getString(id);
@@ -67,18 +65,18 @@ public class DebugInfoParser {
for (RegisterArg arg : mthArgs) {
int rn = arg.getRegNum();
locals[rn] = new LocalVarInfo(arg);
locals[rn] = new LocalVar(arg);
activeRegisters[rn] = arg;
}
addrChange(-1, 1); // process '0' instruction
addrChange(-1, 1, line); // process '0' instruction
int c = section.readByte() & 0xFF;
while (c != DBG_END_SEQUENCE) {
switch (c) {
case DBG_ADVANCE_PC: {
int addrInc = section.readUleb128();
addr = addrChange(addr, addrInc);
addr = addrChange(addr, addrInc, line);
break;
}
case DBG_ADVANCE_LINE: {
@@ -90,7 +88,7 @@ public class DebugInfoParser {
int regNum = section.readUleb128();
int nameId = section.readUleb128() - 1;
int type = section.readUleb128() - 1;
LocalVarInfo var = new LocalVarInfo(dex, regNum, nameId, type, DexNode.NO_INDEX);
LocalVar var = new LocalVar(dex, regNum, nameId, type, DexNode.NO_INDEX);
startVar(var, addr, line);
break;
}
@@ -99,13 +97,13 @@ public class DebugInfoParser {
int nameId = section.readUleb128() - 1;
int type = section.readUleb128() - 1;
int sign = section.readUleb128() - 1;
LocalVarInfo var = new LocalVarInfo(dex, regNum, nameId, type, sign);
LocalVar var = new LocalVar(dex, regNum, nameId, type, sign);
startVar(var, addr, line);
break;
}
case DBG_RESTART_LOCAL: {
int regNum = section.readUleb128();
LocalVarInfo var = locals[regNum];
LocalVar var = locals[regNum];
if (var != null) {
var.end(addr, line);
setVar(var);
@@ -115,7 +113,7 @@ public class DebugInfoParser {
}
case DBG_END_LOCAL: {
int regNum = section.readUleb128();
LocalVarInfo var = locals[regNum];
LocalVar var = locals[regNum];
if (var != null) {
var.end(addr, line);
setVar(var);
@@ -139,21 +137,20 @@ public class DebugInfoParser {
default: {
if (c >= DBG_FIRST_SPECIAL) {
int adjusted_opcode = c - DBG_FIRST_SPECIAL;
line += DBG_LINE_BASE + (adjusted_opcode % DBG_LINE_RANGE);
int addrInc = (adjusted_opcode / DBG_LINE_RANGE);
addr = addrChange(addr, addrInc);
int adjustedOpcode = c - DBG_FIRST_SPECIAL;
line += DBG_LINE_BASE + (adjustedOpcode % DBG_LINE_RANGE);
int addrInc = (adjustedOpcode / DBG_LINE_RANGE);
addr = addrChange(addr, addrInc, line);
} else {
throw new DecodeException("Unknown debug insn code: " + c);
}
break;
}
}
c = section.readByte() & 0xFF;
}
for (LocalVarInfo var : locals) {
for (LocalVar var : locals) {
if (var != null && !var.isEnd()) {
var.end(addr, line);
setVar(var);
@@ -161,16 +158,17 @@ public class DebugInfoParser {
}
}
private int addrChange(int addr, int addrInc) {
private int addrChange(int addr, int addrInc, int line) {
int newAddr = addr + addrInc;
for (int i = addr + 1; i <= newAddr; i++) {
InsnNode insn = insnByOffset[i];
if (insn == null)
continue;
insn.setSourceLine(line);
for (InsnArg arg : insn.getArguments())
if (arg.isRegister()) {
activeRegisters[arg.getRegNum()] = arg;
activeRegisters[((RegisterArg) arg).getRegNum()] = arg;
}
RegisterArg res = insn.getResult();
@@ -180,9 +178,9 @@ public class DebugInfoParser {
return newAddr;
}
private void startVar(LocalVarInfo var, int addr, int line) {
private void startVar(LocalVar var, int addr, int line) {
int regNum = var.getRegNum();
LocalVarInfo prev = locals[regNum];
LocalVar prev = locals[regNum];
if (prev != null && !prev.isEnd()) {
prev.end(addr, line);
setVar(prev);
@@ -191,7 +189,7 @@ public class DebugInfoParser {
locals[regNum] = var;
}
private void setVar(LocalVarInfo var) {
private void setVar(LocalVar var) {
int start = var.getStartAddr();
int end = var.getEndAddr();
@@ -203,7 +201,7 @@ public class DebugInfoParser {
merge(activeRegisters[var.getRegNum()], var);
}
private static void fillLocals(InsnNode insn, LocalVarInfo var) {
private static void fillLocals(InsnNode insn, LocalVar var) {
if (insn.getResult() != null)
merge(insn.getResult(), var);
@@ -211,10 +209,11 @@ public class DebugInfoParser {
merge(arg, var);
}
private static void merge(InsnArg arg, LocalVarInfo var) {
private static void merge(InsnArg arg, LocalVar var) {
if (arg != null && arg.isRegister()) {
if (var.getRegNum() == arg.getRegNum())
arg.setTypedVar(var.getTypedVar());
if (var.getRegNum() == ((RegisterArg) arg).getRegNum()) {
arg.mergeDebugInfo(var);
}
}
}
}
@@ -0,0 +1,112 @@
package jadx.core.dex.nodes.parser;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.nodes.DexNode;
import jadx.core.utils.exceptions.DecodeException;
import java.util.ArrayList;
import java.util.List;
import com.android.dx.io.DexBuffer.Section;
import com.android.dx.io.EncodedValueReader;
import com.android.dx.util.Leb128Utils;
public class EncValueParser extends EncodedValueReader {
private final DexNode dex;
public EncValueParser(DexNode dex, Section in) {
super(in);
this.dex = dex;
}
public Object parseValue() throws DecodeException {
int argAndType = readByte();
int type = argAndType & 0x1F;
int arg = (argAndType & 0xE0) >> 5;
int size = arg + 1;
switch (type) {
case ENCODED_NULL:
return null;
case ENCODED_BOOLEAN:
return arg == 1;
case ENCODED_BYTE:
return in.readByte();
case ENCODED_SHORT:
return (short) parseNumber(size, true);
case ENCODED_CHAR:
return (char) parseUnsignedInt(size);
case ENCODED_INT:
return (int) parseNumber(size, true);
case ENCODED_LONG:
return parseNumber(size, true);
case ENCODED_FLOAT:
return Float.intBitsToFloat((int) parseNumber(size, false, 4));
case ENCODED_DOUBLE:
return Double.longBitsToDouble(parseNumber(size, false, 8));
case ENCODED_STRING:
return dex.getString(parseUnsignedInt(size));
case ENCODED_TYPE:
return dex.getType(parseUnsignedInt(size));
case ENCODED_METHOD:
return MethodInfo.fromDex(dex, parseUnsignedInt(size));
case ENCODED_FIELD:
case ENCODED_ENUM:
return FieldInfo.fromDex(dex, parseUnsignedInt(size));
case ENCODED_ARRAY:
int count = Leb128Utils.readUnsignedLeb128(in);
List<Object> values = new ArrayList<Object>(count);
for (int i = 0; i < count; i++) {
values.add(parseValue());
}
return values;
case ENCODED_ANNOTATION:
return AnnotationsParser.readAnnotation(dex, (Section) in, false);
}
throw new DecodeException("Unknown encoded value type: 0x" + Integer.toHexString(type));
}
private int parseUnsignedInt(int byteCount) {
return (int) parseNumber(byteCount, false, 0);
}
private long parseNumber(int byteCount, boolean isSignExtended) {
return parseNumber(byteCount, isSignExtended, 0);
}
private long parseNumber(int byteCount, boolean isSignExtended, int fillOnRight) {
long result = 0;
long last = 0;
for (int i = 0; i < byteCount; i++) {
last = readByte();
result |= last << i * 8;
}
if (fillOnRight != 0) {
for (int i = byteCount; i < fillOnRight; i++) {
result <<= 8;
}
} else {
if (isSignExtended && (last & 0x80) != 0) {
for (int i = byteCount; i < 8; i++) {
result |= (long) 0xFF << i * 8;
}
}
}
return result;
}
private int readByte() {
return in.readByte() & 0xFF;
}
}

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