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