Compare commits

...

71 Commits

Author SHA1 Message Date
Skylot 671be0af0a add jadx-gui screenshot 2014-08-15 23:08:18 +04:00
Skylot 7e9278f992 don't hardcode maximum Java heap size 2014-08-15 22:39:34 +04:00
Skylot 9194441c47 add ASM to NOTICE file 2014-08-15 22:38:50 +04:00
Skylot 4f307c0085 core: allow subblock replace for 'if' region 2014-08-14 22:38:29 +04:00
Skylot 3bdda55102 core: inline filled array creation 2014-08-14 22:23:13 +04:00
Skylot b657b0fb1f core: fix 'if' processing in 'do/while' loop 2014-08-12 23:00:29 +04:00
Skylot 4935ae6da5 core: hide value parser constants 2014-08-12 22:58:20 +04:00
Skylot 72a50eae43 core: fix missing blocks in loop region 2014-08-11 22:29:10 +04:00
Skylot fa37b90cff core: fix processing try/catch in other catch 2014-08-10 22:36:42 +04:00
Skylot 052a8db606 core: resolve minor issues 2014-08-09 19:32:13 +04:00
Skylot 88ccba166e core: don't inline variables defined in 'try' and used in 'catch' 2014-08-08 22:10:10 +04:00
Skylot 58998089a6 core: redone 'if' structure checking 2014-08-07 22:20:47 +04:00
Skylot f0a73b329e core: fix processing conditions in loop 2014-08-06 22:28:29 +04:00
Skylot c97678a477 refactor: make ErrorsCounter non static 2014-08-05 22:48:31 +04:00
Skylot 2ad739275f core: handle special values for numbers 2014-08-04 22:07:10 +04:00
Skylot caad78885d core: check for duplicated code generation 2014-08-02 16:39:14 +04:00
Skylot a234227b9f core: fix errors in try/catch processing (issue #13) 2014-08-02 16:33:52 +04:00
Skylot 16f736e773 core: fix missing 'catch' code 2014-07-30 23:05:39 +04:00
Skylot 1fe24ad11d travis: cache dependencies 2014-07-29 23:41:10 +04:00
Skylot 33c5e0827a core: always check arguments before inline 2014-07-29 22:59:53 +04:00
Skylot cbd36aeb8f core: fix unused variables declaration 2014-07-29 22:34:18 +04:00
Skylot 2963bb3f41 core: fix issues reported by coverity 2014-07-28 23:19:48 +04:00
Skylot 09a6ceac63 gui: replace underline to color highlight (experimental) 2014-07-28 22:50:55 +04:00
Skylot 75d8a01cab core: improve error reporting 2014-07-28 22:50:55 +04:00
Skylot 0968f75e9a core: fix condition in loops (issue #9) 2014-07-28 22:50:42 +04:00
Skylot bc0db88afa update gradle and dependencies versions 2014-07-18 23:29:36 +04:00
Skylot 5f11f12d0c core: remove redundant spaces for enums 2014-07-18 21:21:24 +04:00
Skylot 2d18950542 core: add some integration tests 2014-07-17 23:32:18 +04:00
Skylot 50d314445a core: fix code style 2014-07-17 23:31:07 +04:00
Skylot f8d57d9265 core: decompile '.class' files 2014-07-15 23:45:25 +04:00
Skylot ebbe6db378 core: fix complex 'if' processing (issues #9 and #12) 2014-07-12 21:26:14 +04:00
skylot 543cad3a23 Merge pull request #11 from Fruiter/master
core: fix nested try-catch blocks processing
2014-07-07 21:13:23 +04:00
fruiter 41cc83dbf6 core: fix nested try-catch blocks processing 2014-07-06 20:15:20 -04:00
Skylot ce7101be88 core: always inline 'this' (issue #10) 2014-06-28 15:39:35 +04:00
Skylot 0a241e3a9c core tests: add custom string matchers 2014-06-28 15:38:50 +04:00
Skylot 37857e88ea core: fix switch statement processing (issue #9 case 2) 2014-06-24 14:08:20 +04:00
Skylot 6fbcf46a8b core: refactor return remover visitor 2014-06-24 14:08:20 +04:00
Skylot a36bc8f29a core: add serial uid to JadxRuntimeException 2014-06-24 14:08:20 +04:00
Skylot 813b7bca6e core: sort error nodes in execution report 2014-06-23 23:37:39 +04:00
Skylot e2945f2a42 core: limit region traversal iterations count 2014-06-23 23:37:39 +04:00
Skylot eaf623a560 gui: fix sync with editor 2014-06-20 20:20:35 +04:00
Skylot 26aa504590 core: guard endless regions processing 2014-06-20 17:08:07 +04:00
Skylot e4dde3f4b6 core: fix class cast exception 2014-06-15 21:35:34 +04:00
Skylot 9c90699c40 core: fix parsing of generic signature with inner classes 2014-06-15 17:42:48 +04:00
Skylot b67cd50e8a gui: add definitions search window 2014-06-14 19:38:24 +04:00
Skylot d2acaa03f5 core: guess variable name from assign instruction 2014-06-06 21:22:20 +04:00
Skylot f2aa4cd10b core: make better variables naming 2014-06-05 19:40:57 +04:00
Skylot b940b99e75 core: fix issues in variable names and try/catch blocks 2014-06-03 23:08:40 +04:00
Skylot 868e0706ea core: fix source line number parsing and saving 2014-05-31 16:58:03 +04:00
Skylot 324f544ba2 gui: show source line numbers 2014-05-31 16:17:40 +04:00
Skylot 0a1981f90e gui: add hyperlinks for classes and fields 2014-05-25 23:14:29 +04:00
Skylot 0a36bfb088 core: fix try-catch blocks processing 2014-05-24 19:55:29 +04:00
Skylot 0d94af099b core: improve 'if' detection with 'return' instruction 2014-05-18 00:28:43 +04:00
Skylot 4a6115ed64 core: refactor attribute storage 2014-05-14 21:12:39 +04:00
Skylot 42eb319751 fix issues reported by Coverity 2014-05-12 22:08:33 +04:00
Skylot 343bddc6ad core: fix 'break' detection in loop 2014-05-12 22:08:33 +04:00
Skylot 632a742ea9 core: fix method inline 2014-05-12 22:08:33 +04:00
Skylot 08c9d1228a core: inline 'cmp' instruction 2014-05-12 22:08:33 +04:00
Skylot 11d8b28fb4 core: fix variable rename 2014-05-12 22:08:33 +04:00
Skylot 12b6371209 core: fix basic block initial id 2014-05-12 22:08:33 +04:00
Skylot 24d22aaafb core: fix 'if' detection 2014-05-12 22:08:33 +04:00
Skylot ebf7822628 use spock framework for unit tests 2014-05-12 22:08:33 +04:00
Skylot 7669fa1582 update dependencies, add coveralls badge 2014-05-12 22:08:33 +04:00
Skylot e49ba61917 core: use SSA representation for instruction arguments 2014-05-12 22:08:32 +04:00
Skylot 96db1c2479 core: reformat code 2014-05-12 21:59:37 +04:00
skylot 7abdb41a9e Merge pull request #7 from bkerler/master
Add support for dx 1.8 library
2014-05-12 21:28:55 +04:00
Bjoern Kerler 14f6d2f3b0 Add support for dx 1.8 library 2014-05-12 09:12:34 +02:00
Skylot 4e4b4975ad core: fix method redecompilation (issue #6) 2014-04-24 23:44:30 +04:00
skylot 93fafcf886 Merge pull request #5 from dnet/master
removed unused private method getCodePanel(int)
2014-04-02 22:03:57 +04:00
András Veres-Szentkirályi 82cc88a1b9 removed unused private method getCodePanel(int) 2014-04-02 17:10:34 +02:00
Skylot 5c94e0bccc core: improve exceptions handling 2014-03-27 23:24:20 +04:00
305 changed files with 9805 additions and 3446 deletions
+10
View File
@@ -3,10 +3,20 @@ jdk:
- oraclejdk7
- openjdk7
- openjdk6
before_install:
- chmod +x gradlew
script:
- TERM=dumb ./gradlew clean build dist
after_success:
- TERM=dumb ./gradlew jacocoTestReport coveralls
cache:
directories:
- $HOME/.gradle
notifications:
email:
- skylot@gmail.com
+35
View File
@@ -105,6 +105,41 @@ as published by the Free Software Foundation.
*******************************************************************************
ASM library:
*******************************************************************************
Copyright (c) 2000-2011 INRIA, France Telecom
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holders nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
THE POSSIBILITY OF SUCH DAMAGE.
*******************************************************************************
Jadx-gui components
===================
+23 -10
View File
@@ -1,24 +1,27 @@
## JADX
## JADX
[![Build Status](https://travis-ci.org/skylot/jadx.png?branch=master)](https://travis-ci.org/skylot/jadx)
[![Build Status](https://drone.io/github.com/skylot/jadx/status.png)](https://drone.io/github.com/skylot/jadx/latest)
[![Coverage Status](https://coveralls.io/repos/skylot/jadx/badge.png)](https://coveralls.io/r/skylot/jadx)
[![Coverity Scan Build Status](https://scan.coverity.com/projects/2166/badge.svg)](https://scan.coverity.com/projects/2166)
**jadx** - Dex to Java decompiler
Command line and GUI tools for produce Java source code from Android Dex and Apk files
Note: jadx-gui now in experimental stage
![jadx-gui screenshot](http://skylot.github.io/jadx/jadx-gui.png)
### Downloads
- [unstable](https://drone.io/github.com/skylot/jadx/files)
[![Build Status](https://drone.io/github.com/skylot/jadx/status.png)](https://drone.io/github.com/skylot/jadx/latest)
[![Build Status](https://travis-ci.org/skylot/jadx.png?branch=master)](https://travis-ci.org/skylot/jadx)
- from [github](https://github.com/skylot/jadx/releases)
- from [sourceforge](http://sourceforge.net/projects/jadx/files/)
- from [sourceforge](http://sourceforge.net/projects/jadx/files/)
### Building from source
### Building from source
git clone https://github.com/skylot/jadx.git
cd jadx
./gradlew dist
(on Windows, use `gradlew.bat` instead of `./gradlew`)
Scripts for run jadx will be placed in `build/jadx/bin`
@@ -36,7 +39,7 @@ Run **jadx** on itself:
### Usage
```
jadx[-gui] [options] <input file> (.dex, .apk or .jar)
jadx[-gui] [options] <input file> (.dex, .apk, .jar or .class)
options:
-d, --output-dir - output directory
-j, --threads-count - processing threads count
@@ -49,6 +52,16 @@ Example:
jadx -d out classes.dex
```
### Troubleshooting
##### Out of memory error:
- Reduce processing threads count (`-j` option)
- Increase maximum java heap size:
* command line (example for linux):
`JAVA_OPTS="-Xmx4G" jadx -j 1 some.apk`
* edit 'jadx' script (jadx.bat on Windows) and setup bigger heap size:
`DEFAULT_JVM_OPTS="-Xmx2500M"`
---------------------------------------
*Licensed under the Apache 2.0 License*
*Copyright 2013 by Skylot*
*Copyright 2014 by Skylot*
+32 -13
View File
@@ -5,20 +5,18 @@ apply plugin: 'sonar-runner'
subprojects {
apply plugin: 'java'
apply plugin: 'groovy'
apply plugin: 'jacoco'
apply plugin: 'idea'
apply plugin: 'eclipse'
sourceCompatibility = 1.6
targetCompatibility = 1.6
apply plugin: 'coveralls'
version = jadxVersion
gradle.projectsEvaluated {
tasks.withType(Compile) {
if (!"${it}".contains(':jadx-samples:')) {
options.compilerArgs << '-Xlint' << '-Xlint:unchecked' << '-Xlint:deprecation'
}
tasks.withType(JavaCompile) {
sourceCompatibility = JavaVersion.VERSION_1_6
targetCompatibility = JavaVersion.VERSION_1_6
if (!"$it".contains(':jadx-samples:')) {
options.compilerArgs << '-Xlint' << '-Xlint:unchecked' << '-Xlint:deprecation'
}
}
@@ -30,15 +28,36 @@ subprojects {
}
dependencies {
compile 'org.slf4j:slf4j-api:1.7.6'
compile 'org.slf4j:slf4j-api:1.7.7'
testCompile 'ch.qos.logback:logback-classic:1.1.2'
testCompile 'junit:junit:4.11'
testCompile 'org.mockito:mockito-core:1.9.5'
testCompile 'ch.qos.logback:logback-classic:1.1.1'
testCompile 'org.spockframework:spock-core:0.7-groovy-2.0'
testCompile 'cglib:cglib-nodep:3.1'
}
repositories {
mavenCentral()
}
jacocoTestReport {
reports {
xml.enabled = true // coveralls plugin depends on xml format report
html.enabled = true
}
}
}
buildscript {
repositories {
mavenCentral()
}
dependencies {
// setup coveralls (http://coveralls.io/) see http://github.com/kt3k/coveralls-gradle-plugin
classpath 'org.kt3k.gradle.plugin:coveralls-gradle-plugin:0.6.1'
}
}
task copyArtifacts(type: Sync, dependsOn: ['jadx-cli:installApp', 'jadx-gui:installApp']) {
@@ -69,5 +88,5 @@ task clean(type: Delete) {
}
task wrapper(type: Wrapper) {
gradleVersion = '1.11'
gradleVersion = '2.0'
}
Binary file not shown.
+1 -1
View File
@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=http\://services.gradle.org/distributions/gradle-1.11-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-2.0-bin.zip
+2 -12
View File
@@ -5,18 +5,8 @@ applicationName = 'jadx'
dependencies {
compile(project(':jadx-core'))
compile 'com.beust:jcommander:1.30'
compile 'ch.qos.logback:logback-classic:1.1.1'
}
startScripts {
doLast {
// increase default max heap size
String var = 'DEFAULT_JVM_OPTS='
String args = '-Xmx1300M'
unixScript.text = unixScript.text.replace(var + '""', var + '"' + args + '"')
windowsScript.text = windowsScript.text.replace(var, var + args)
}
compile 'com.beust:jcommander:1.35'
compile 'ch.qos.logback:logback-classic:1.1.2'
}
applicationDistribution.with {
+23 -23
View File
@@ -1,7 +1,6 @@
package jadx.cli;
import jadx.api.Decompiler;
import jadx.core.utils.ErrorsCounter;
import jadx.api.JadxDecompiler;
import jadx.core.utils.exceptions.JadxException;
import java.io.File;
@@ -12,39 +11,39 @@ import org.slf4j.LoggerFactory;
public class JadxCLI {
private static final Logger LOG = LoggerFactory.getLogger(JadxCLI.class);
public static void main(String[] args) {
public static void main(String[] args) throws JadxException {
try {
JadxCLIArgs jadxArgs = new JadxCLIArgs(args);
checkArgs(jadxArgs);
processAndSave(jadxArgs);
} catch (Exception e) {
LOG.error(e.getMessage());
JadxCLIArgs jadxArgs = new JadxCLIArgs();
if (processArgs(jadxArgs, args)) {
processAndSave(jadxArgs);
}
} catch (Throwable e) {
LOG.error("jadx error: " + e.getMessage(), e);
System.exit(1);
}
}
private static void processAndSave(JadxCLIArgs jadxArgs) {
try {
Decompiler jadx = new Decompiler(jadxArgs);
jadx.loadFiles(jadxArgs.getInput());
jadx.setOutputDir(jadxArgs.getOutDir());
jadx.save();
static void processAndSave(JadxCLIArgs jadxArgs) throws JadxException {
JadxDecompiler jadx = new JadxDecompiler(jadxArgs);
jadx.setOutputDir(jadxArgs.getOutDir());
jadx.loadFiles(jadxArgs.getInput());
jadx.save();
if (jadx.getErrorsCount() != 0) {
jadx.printErrorsReport();
LOG.error("finished with errors");
} else {
LOG.info("done");
} catch (Throwable e) {
LOG.error("jadx error:", e);
}
int errorsCount = ErrorsCounter.getErrorCount();
if (errorsCount != 0) {
ErrorsCounter.printReport();
}
System.exit(errorsCount);
}
private static void checkArgs(JadxCLIArgs jadxArgs) throws JadxException {
static boolean processArgs(JadxCLIArgs jadxArgs, String[] args) throws JadxException {
if (!jadxArgs.processArgs(args)) {
return false;
}
if (jadxArgs.getInput().isEmpty()) {
LOG.error("Please specify input file");
jadxArgs.printUsage();
System.exit(1);
return false;
}
File outputDir = jadxArgs.getOutDir();
if (outputDir == null) {
@@ -64,5 +63,6 @@ public class JadxCLI {
if (outputDir.exists() && !outputDir.isDirectory()) {
throw new JadxException("Output directory exists as file " + outputDir);
}
return true;
}
}
@@ -1,7 +1,7 @@
package jadx.cli;
import jadx.api.IJadxArgs;
import jadx.core.Consts;
import jadx.api.JadxDecompiler;
import jadx.core.utils.exceptions.JadxException;
import java.io.File;
@@ -20,7 +20,7 @@ import com.beust.jcommander.ParameterException;
public final class JadxCLIArgs implements IJadxArgs {
@Parameter(description = "<input file> (.dex, .apk or .jar)")
@Parameter(description = "<input file> (.dex, .apk, .jar or .class)")
protected List<String> files;
@Parameter(names = {"-d", "--output-dir"}, description = "output directory")
@@ -47,25 +47,25 @@ public final class JadxCLIArgs implements IJadxArgs {
private final List<File> input = new ArrayList<File>(1);
private File outputDir;
public JadxCLIArgs(String[] args) {
parse(args);
processArgs();
public boolean processArgs(String[] args) {
return parse(args) && process();
}
private void parse(String[] args) {
private boolean parse(String[] args) {
try {
new JCommander(this, args);
return true;
} catch (ParameterException e) {
System.err.println("Arguments parse error: " + e.getMessage());
printUsage();
System.exit(1);
return false;
}
}
public void processArgs() {
private boolean process() {
if (isPrintHelp()) {
printUsage();
System.exit(0);
return false;
}
try {
if (threadsCount <= 0) {
@@ -95,8 +95,9 @@ public final class JadxCLIArgs implements IJadxArgs {
} catch (JadxException e) {
System.err.println("ERROR: " + e.getMessage());
printUsage();
System.exit(1);
return false;
}
return true;
}
public void printUsage() {
@@ -104,7 +105,7 @@ public final class JadxCLIArgs implements IJadxArgs {
// print usage in not sorted fields order (by default its sorted by description)
PrintStream out = System.out;
out.println();
out.println("jadx - dex to java decompiler, version: " + Consts.JADX_VERSION);
out.println("jadx - dex to java decompiler, version: " + JadxDecompiler.getVersion());
out.println();
out.println("usage: jadx [options] " + jc.getMainParameterDescription());
out.println("options:");
@@ -147,6 +148,7 @@ public final class JadxCLIArgs implements IJadxArgs {
return input;
}
@Override
public File getOutDir() {
return outputDir;
}
+7 -2
View File
@@ -1,8 +1,13 @@
ext.jadxClasspath = 'clsp-data/android-4.3.jar'
dependencies {
compile 'com.google.android.tools:dx:1.7'
compile files('lib/dx-1.8.jar')
compile 'org.ow2.asm:asm:5.0.3'
runtime files(jadxClasspath)
}
task packTests(type: Jar) {
classifier = 'tests'
from sourceSets.test.output
}
Binary file not shown.
@@ -30,6 +30,10 @@ public final class CodePosition {
return offset;
}
public boolean isSet() {
return line != 0 || offset != 0;
}
@Override
public boolean equals(Object o) {
if (this == o) {
@@ -1,7 +1,14 @@
package jadx.api;
import java.io.File;
public class DefaultJadxArgs implements IJadxArgs {
@Override
public File getOutDir() {
return new File("jadx-output");
}
@Override
public int getThreadsCount() {
return Runtime.getRuntime().availableProcessors();
@@ -1,6 +1,10 @@
package jadx.api;
import java.io.File;
public interface IJadxArgs {
File getOutDir();
int getThreadsCount();
boolean isCFGOutput();
@@ -7,8 +7,8 @@ import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.visitors.IDexTreeVisitor;
import jadx.core.dex.visitors.SaveCode;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.utils.files.InputFile;
@@ -22,7 +22,6 @@ import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
@@ -31,7 +30,7 @@ import org.slf4j.LoggerFactory;
/**
* Jadx API usage example:
* <pre><code>
* Decompiler jadx = new Decompiler();
* JadxDecompiler jadx = new JadxDecompiler();
* jadx.loadFile(new File("classes.dex"));
* jadx.setOutputDir(new File("out"));
* jadx.save();
@@ -44,8 +43,8 @@ import org.slf4j.LoggerFactory;
* }
* </code></pre>
*/
public final class Decompiler {
private static final Logger LOG = LoggerFactory.getLogger(Decompiler.class);
public final class JadxDecompiler {
private static final Logger LOG = LoggerFactory.getLogger(JadxDecompiler.class);
private final IJadxArgs args;
private final List<InputFile> inputFiles = new ArrayList<InputFile>();
@@ -56,13 +55,14 @@ public final class Decompiler {
private List<IDexTreeVisitor> passes;
private List<JavaClass> classes;
public Decompiler() {
this.args = new DefaultJadxArgs();
init();
public JadxDecompiler() {
this(new DefaultJadxArgs());
}
public Decompiler(IJadxArgs jadxArgs) {
public JadxDecompiler(IJadxArgs jadxArgs) {
this.args = jadxArgs;
this.outDir = jadxArgs.getOutDir();
reset();
init();
}
@@ -73,22 +73,36 @@ public final class Decompiler {
void init() {
if (outDir == null) {
outDir = new File("jadx-output");
outDir = new DefaultJadxArgs().getOutDir();
}
this.passes = Jadx.getPassesList(args, outDir);
}
public void loadFile(File file) throws IOException, DecodeException {
void reset() {
ClassInfo.clearCache();
classes = null;
root = null;
}
public static String getVersion() {
return Jadx.getVersion();
}
public void loadFile(File file) throws JadxException {
loadFiles(Collections.singletonList(file));
}
public void loadFiles(List<File> files) throws IOException, DecodeException {
public void loadFiles(List<File> files) throws JadxException {
if (files.isEmpty()) {
throw new JadxRuntimeException("Empty file list");
throw new JadxException("Empty file list");
}
inputFiles.clear();
for (File file : files) {
inputFiles.add(new InputFile(file));
try {
inputFiles.add(new InputFile(file));
} catch (IOException e) {
throw new JadxException("Error load file: " + file, e);
}
}
parse();
}
@@ -99,28 +113,25 @@ public final class Decompiler {
ex.shutdown();
ex.awaitTermination(1, TimeUnit.DAYS);
} catch (InterruptedException e) {
LOG.error("Save interrupted", e);
throw new JadxRuntimeException("Save interrupted", e);
}
}
public ThreadPoolExecutor getSaveExecutor() {
public ExecutorService getSaveExecutor() {
if (root == null) {
throw new JadxRuntimeException("No loaded files");
}
int threadsCount = args.getThreadsCount();
LOG.debug("processing threads count: {}", threadsCount);
final List<IDexTreeVisitor> passList = new ArrayList<IDexTreeVisitor>(passes);
SaveCode savePass = new SaveCode(outDir, args);
passList.add(savePass);
LOG.info("processing ...");
ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(threadsCount);
for (final ClassNode cls : root.getClasses(false)) {
ExecutorService executor = Executors.newFixedThreadPool(threadsCount);
for (final JavaClass cls : getClasses()) {
executor.execute(new Runnable() {
@Override
public void run() {
ProcessClass.process(cls, passList);
cls.decompile();
SaveCode.save(outDir, args, cls.getClassNode());
}
});
}
@@ -128,11 +139,14 @@ public final class Decompiler {
}
public List<JavaClass> getClasses() {
if (root == null) {
return Collections.emptyList();
}
if (classes == null) {
List<ClassNode> classNodeList = root.getClasses(false);
List<JavaClass> clsList = new ArrayList<JavaClass>(classNodeList.size());
for (ClassNode classNode : classNodeList) {
clsList.add(new JavaClass(this, classNode));
clsList.add(new JavaClass(classNode, this));
}
classes = Collections.unmodifiableList(clsList);
}
@@ -140,8 +154,12 @@ public final class Decompiler {
}
public List<JavaPackage> getPackages() {
List<JavaClass> classList = getClasses();
if (classList.isEmpty()) {
return Collections.emptyList();
}
Map<String, List<JavaClass>> map = new HashMap<String, List<JavaClass>>();
for (JavaClass javaClass : getClasses()) {
for (JavaClass javaClass : classList) {
String pkg = javaClass.getPackage();
List<JavaClass> clsList = map.get(pkg);
if (clsList == null) {
@@ -159,7 +177,7 @@ public final class Decompiler {
Collections.sort(pkg.getClasses(), new Comparator<JavaClass>() {
@Override
public int compare(JavaClass o1, JavaClass o2) {
return o1.getShortName().compareTo(o2.getShortName());
return o1.getName().compareTo(o2.getName());
}
});
}
@@ -167,7 +185,17 @@ public final class Decompiler {
}
public int getErrorsCount() {
return ErrorsCounter.getErrorCount();
if (root == null) {
return 0;
}
return root.getErrorsCounter().getErrorCount();
}
public void printErrorsReport() {
if (root == null) {
return;
}
root.getErrorsCounter().printReport();
}
void parse() throws DecodeException {
@@ -177,14 +205,7 @@ public final class Decompiler {
root.load(inputFiles);
}
private void reset() {
ClassInfo.clearCache();
ErrorsCounter.reset();
classes = null;
}
void processClass(ClassNode cls) {
LOG.info("processing class {} ...", cls);
ProcessClass.process(cls, passes);
}
@@ -203,4 +224,9 @@ public final class Decompiler {
}
return null;
}
@Override
public String toString() {
return "jadx decompiler " + getVersion();
}
}
+45 -17
View File
@@ -1,13 +1,12 @@
package jadx.api;
import jadx.core.codegen.CodeWriter;
import jadx.core.dex.attributes.AttributeFlag;
import jadx.core.dex.attributes.LineAttrNode;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.nodes.LineAttrNode;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.exceptions.JadxRuntimeException;
import java.util.ArrayList;
import java.util.Collections;
@@ -15,18 +14,29 @@ import java.util.Comparator;
import java.util.List;
import java.util.Map;
public final class JavaClass {
public final class JavaClass implements JavaNode {
private final Decompiler decompiler;
private final JadxDecompiler decompiler;
private final ClassNode cls;
private final JavaClass parent;
private List<JavaClass> innerClasses = Collections.emptyList();
private List<JavaField> fields = Collections.emptyList();
private List<JavaMethod> methods = Collections.emptyList();
JavaClass(Decompiler decompiler, ClassNode classNode) {
JavaClass(ClassNode classNode, JadxDecompiler decompiler) {
this.decompiler = decompiler;
this.cls = classNode;
this.parent = null;
}
/**
* Inner classes constructor
*/
JavaClass(ClassNode classNode, JavaClass parent) {
this.decompiler = null;
this.cls = classNode;
this.parent = parent;
}
public String getCode() {
@@ -35,12 +45,15 @@ public final class JavaClass {
decompile();
code = cls.getCode();
}
return code != null ? code.toString() : "error processing class";
if (code == null) {
return "";
}
return code.toString();
}
public void decompile() {
if (decompiler == null) {
throw new JadxRuntimeException("Can't decompile inner class");
return;
}
if (cls.getCode() == null) {
decompiler.processClass(cls);
@@ -57,8 +70,8 @@ public final class JavaClass {
if (inClsCount != 0) {
List<JavaClass> list = new ArrayList<JavaClass>(inClsCount);
for (ClassNode inner : cls.getInnerClasses()) {
if (!inner.getAttributes().contains(AttributeFlag.DONT_GENERATE)) {
JavaClass javaClass = new JavaClass(null, inner);
if (!inner.contains(AFlag.DONT_GENERATE)) {
JavaClass javaClass = new JavaClass(inner, this);
javaClass.load();
list.add(javaClass);
}
@@ -70,8 +83,8 @@ public final class JavaClass {
if (fieldsCount != 0) {
List<JavaField> flds = new ArrayList<JavaField>(fieldsCount);
for (FieldNode f : cls.getFields()) {
if (!f.getAttributes().contains(AttributeFlag.DONT_GENERATE)) {
flds.add(new JavaField(f));
if (!f.contains(AFlag.DONT_GENERATE)) {
flds.add(new JavaField(f, this));
}
}
this.fields = Collections.unmodifiableList(flds);
@@ -81,7 +94,7 @@ public final class JavaClass {
if (methodsCount != 0) {
List<JavaMethod> mths = new ArrayList<JavaMethod>(methodsCount);
for (MethodNode m : cls.getMethods()) {
if (!m.getAttributes().contains(AttributeFlag.DONT_GENERATE)) {
if (!m.contains(AFlag.DONT_GENERATE)) {
mths.add(new JavaMethod(this, m));
}
}
@@ -127,31 +140,46 @@ public final class JavaClass {
return null;
}
public Integer getSourceLine(int decompiledLine) {
decompile();
return cls.getCode().getLineMapping().get(decompiledLine);
}
@Override
public String getName() {
return cls.getShortName();
}
@Override
public String getFullName() {
return cls.getFullName();
}
public String getShortName() {
return cls.getShortName();
}
public String getPackage() {
return cls.getPackage();
}
@Override
public JavaClass getDeclaringClass() {
return parent;
}
public AccessInfo getAccessInfo() {
return cls.getAccessFlags();
}
public List<JavaClass> getInnerClasses() {
decompile();
return innerClasses;
}
public List<JavaField> getFields() {
decompile();
return fields;
}
public List<JavaMethod> getMethods() {
decompile();
return methods;
}
@@ -4,18 +4,31 @@ import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.FieldNode;
public final class JavaField {
public final class JavaField implements JavaNode {
private final FieldNode field;
private final JavaClass parent;
public JavaField(FieldNode f) {
JavaField(FieldNode f, JavaClass cls) {
this.field = f;
this.parent = cls;
}
@Override
public String getName() {
return field.getName();
}
@Override
public String getFullName() {
return parent.getFullName() + "." + field.getName();
}
@Override
public JavaClass getDeclaringClass() {
return parent;
}
public AccessInfo getAccessFlags() {
return field.getAccessFlags();
}
@@ -6,19 +6,26 @@ import jadx.core.dex.nodes.MethodNode;
import java.util.List;
public final class JavaMethod {
public final class JavaMethod implements JavaNode {
private final MethodNode mth;
private final JavaClass parent;
public JavaMethod(JavaClass cls, MethodNode m) {
JavaMethod(JavaClass cls, MethodNode m) {
this.parent = cls;
this.mth = m;
}
@Override
public String getName() {
return mth.getMethodInfo().getName();
return mth.getName();
}
@Override
public String getFullName() {
return mth.getMethodInfo().getFullName();
}
@Override
public JavaClass getDeclaringClass() {
return parent;
}
@@ -0,0 +1,10 @@
package jadx.api;
public interface JavaNode {
String getName();
String getFullName();
JavaClass getDeclaringClass();
}
@@ -2,7 +2,7 @@ package jadx.api;
import java.util.List;
public final class JavaPackage implements Comparable<JavaPackage> {
public final class JavaPackage implements JavaNode, Comparable<JavaPackage> {
private final String name;
private final List<JavaClass> classes;
@@ -11,14 +11,26 @@ public final class JavaPackage implements Comparable<JavaPackage> {
this.classes = classes;
}
@Override
public String getName() {
return name;
}
@Override
public String getFullName() {
// TODO: store full package name
return name;
}
public List<JavaClass> getClasses() {
return classes;
}
@Override
public JavaClass getDeclaringClass() {
return null;
}
@Override
public int compareTo(JavaPackage o) {
return name.compareTo(o.name);
@@ -1,8 +1,6 @@
package jadx.core;
public class Consts {
public static final String JADX_VERSION = Jadx.getVersion();
public static final boolean DEBUG = false;
public static final String CLASS_OBJECT = "java.lang.Object";
+25 -15
View File
@@ -6,6 +6,7 @@ import jadx.core.dex.visitors.BlockMakerVisitor;
import jadx.core.dex.visitors.ClassModifier;
import jadx.core.dex.visitors.CodeShrinker;
import jadx.core.dex.visitors.ConstInlinerVisitor;
import jadx.core.dex.visitors.DebugInfoVisitor;
import jadx.core.dex.visitors.DotGraphVisitor;
import jadx.core.dex.visitors.EnumVisitor;
import jadx.core.dex.visitors.FallbackModeVisitor;
@@ -13,17 +14,20 @@ import jadx.core.dex.visitors.IDexTreeVisitor;
import jadx.core.dex.visitors.MethodInlineVisitor;
import jadx.core.dex.visitors.ModVisitor;
import jadx.core.dex.visitors.PrepareForCodeGen;
import jadx.core.dex.visitors.ReSugarCode;
import jadx.core.dex.visitors.SimplifyVisitor;
import jadx.core.dex.visitors.regions.CheckRegions;
import jadx.core.dex.visitors.regions.IfRegionVisitor;
import jadx.core.dex.visitors.regions.ProcessVariables;
import jadx.core.dex.visitors.regions.RegionMakerVisitor;
import jadx.core.dex.visitors.typeresolver.FinishTypeResolver;
import jadx.core.dex.visitors.typeresolver.TypeResolver;
import jadx.core.dex.visitors.regions.ReturnVisitor;
import jadx.core.dex.visitors.ssa.EliminatePhiNodes;
import jadx.core.dex.visitors.ssa.SSATransform;
import jadx.core.dex.visitors.typeinference.FinishTypeInference;
import jadx.core.dex.visitors.typeinference.TypeInference;
import jadx.core.utils.Utils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
@@ -51,26 +55,29 @@ public class Jadx {
passes.add(new FallbackModeVisitor());
} else {
passes.add(new BlockMakerVisitor());
passes.add(new TypeResolver());
passes.add(new SSATransform());
passes.add(new DebugInfoVisitor());
passes.add(new TypeInference());
if (args.isRawCFGOutput()) {
passes.add(new DotGraphVisitor(outDir, false, true));
}
passes.add(new ConstInlinerVisitor());
passes.add(new FinishTypeResolver());
passes.add(new FinishTypeInference());
passes.add(new EliminatePhiNodes());
passes.add(new ModVisitor());
passes.add(new EnumVisitor());
passes.add(new CodeShrinker());
passes.add(new ReSugarCode());
if (args.isCFGOutput()) {
passes.add(new DotGraphVisitor(outDir, false));
}
passes.add(new CodeShrinker());
passes.add(new RegionMakerVisitor());
passes.add(new IfRegionVisitor());
passes.add(new ReturnVisitor());
passes.add(new CodeShrinker());
passes.add(new SimplifyVisitor());
@@ -91,15 +98,18 @@ public class Jadx {
public static String getVersion() {
try {
Enumeration<URL> resources = Utils.class.getClassLoader().getResources("META-INF/MANIFEST.MF");
while (resources.hasMoreElements()) {
Manifest manifest = new Manifest(resources.nextElement().openStream());
String ver = manifest.getMainAttributes().getValue("jadx-version");
if (ver != null) {
return ver;
ClassLoader classLoader = Utils.class.getClassLoader();
if (classLoader != null) {
Enumeration<URL> resources = classLoader.getResources("META-INF/MANIFEST.MF");
while (resources.hasMoreElements()) {
Manifest manifest = new Manifest(resources.nextElement().openStream());
String ver = manifest.getMainAttributes().getValue("jadx-version");
if (ver != null) {
return ver;
}
}
}
} catch (IOException e) {
} catch (Exception e) {
LOG.error("Can't get manifest file", e);
}
return "dev";
@@ -3,7 +3,6 @@ package jadx.core;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.visitors.DepthTraversal;
import jadx.core.dex.visitors.IDexTreeVisitor;
import jadx.core.utils.exceptions.DecodeException;
import java.util.List;
@@ -22,8 +21,6 @@ public final class ProcessClass {
for (IDexTreeVisitor visitor : passes) {
DepthTraversal.visit(visitor, cls);
}
} catch (DecodeException e) {
LOG.error("Decode exception: " + cls, e);
} catch (Exception e) {
LOG.error("Class process exception: " + cls, e);
} finally {
@@ -65,6 +65,9 @@ public class ClsSet {
for (ClassNode cls : list) {
if (cls.getAccessFlags().isPublic()) {
NClass nClass = getCls(cls.getRawName(), names);
if (nClass == null) {
throw new JadxRuntimeException("Missing class: " + cls);
}
nClass.setParents(makeParentsArray(cls, names));
classes[k] = nClass;
k++;
@@ -102,40 +105,47 @@ public class ClsSet {
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();
try {
String outputName = output.getName();
if (outputName.endsWith(CLST_EXTENSION)) {
save(outputStream);
} else if (outputName.endsWith(".jar")) {
ZipOutputStream out = new ZipOutputStream(outputStream);
try {
out.putNextEntry(new ZipEntry(CLST_PKG_PATH + "/" + CLST_FILENAME));
save(out);
out.closeEntry();
} finally {
out.close();
}
} else {
throw new JadxRuntimeException("Unknown file format: " + outputName);
}
} else {
throw new JadxRuntimeException("Unknown file format: " + outputName);
} finally {
outputStream.close();
}
}
public void save(OutputStream output) throws IOException {
DataOutputStream out = new DataOutputStream(output);
out.writeBytes(JADX_CLS_SET_HEADER);
out.writeByte(VERSION);
try {
out.writeBytes(JADX_CLS_SET_HEADER);
out.writeByte(VERSION);
LOG.info("Classes count: " + classes.length);
out.writeInt(classes.length);
for (NClass cls : classes) {
writeString(out, cls.getName());
}
for (NClass cls : classes) {
NClass[] parents = cls.getParents();
out.writeByte(parents.length);
for (NClass parent : parents) {
out.writeInt(parent.getId());
LOG.info("Classes count: " + classes.length);
out.writeInt(classes.length);
for (NClass cls : classes) {
writeString(out, cls.getName());
}
for (NClass cls : classes) {
NClass[] parents = cls.getParents();
out.writeByte(parents.length);
for (NClass parent : parents) {
out.writeInt(parent.getId());
}
}
} finally {
out.close();
}
}
@@ -144,55 +154,67 @@ public class ClsSet {
if (input == null) {
throw new JadxRuntimeException("Can't load classpath file: " + CLST_FILENAME);
}
load(input);
try {
load(input);
} finally {
input.close();
}
}
public void load(File input) throws IOException, DecodeException {
String name = input.getName();
InputStream inputStream = new FileInputStream(input);
if (name.endsWith(CLST_EXTENSION)) {
load(inputStream);
} else if (name.endsWith(".jar")) {
ZipInputStream in = new ZipInputStream(inputStream);
try {
ZipEntry entry = in.getNextEntry();
while (entry != null) {
if (entry.getName().endsWith(CLST_EXTENSION)) {
load(in);
try {
if (name.endsWith(CLST_EXTENSION)) {
load(inputStream);
} else if (name.endsWith(".jar")) {
ZipInputStream in = new ZipInputStream(inputStream);
try {
ZipEntry entry = in.getNextEntry();
while (entry != null) {
if (entry.getName().endsWith(CLST_EXTENSION)) {
load(in);
}
entry = in.getNextEntry();
}
entry = in.getNextEntry();
} finally {
in.close();
}
} finally {
in.close();
} else {
throw new JadxRuntimeException("Unknown file format: " + name);
}
} else {
throw new JadxRuntimeException("Unknown file format: " + name);
} finally {
inputStream.close();
}
}
public void load(InputStream input) throws IOException, DecodeException {
DataInputStream in = new DataInputStream(input);
byte[] header = new byte[JADX_CLS_SET_HEADER.length()];
int readHeaderLength = in.read(header);
int version = in.readByte();
if (readHeaderLength != JADX_CLS_SET_HEADER.length()
|| !JADX_CLS_SET_HEADER.equals(new String(header, STRING_CHARSET))
|| version != VERSION) {
throw new DecodeException("Wrong jadx class set header");
}
int count = in.readInt();
classes = new NClass[count];
for (int i = 0; i < count; i++) {
String name = readString(in);
classes[i] = new NClass(name, i);
}
for (int i = 0; i < count; i++) {
int pCount = in.readByte();
NClass[] parents = new NClass[pCount];
for (int j = 0; j < pCount; j++) {
parents[j] = classes[in.readInt()];
try {
byte[] header = new byte[JADX_CLS_SET_HEADER.length()];
int readHeaderLength = in.read(header);
int version = in.readByte();
if (readHeaderLength != JADX_CLS_SET_HEADER.length()
|| !JADX_CLS_SET_HEADER.equals(new String(header, STRING_CHARSET))
|| version != VERSION) {
throw new DecodeException("Wrong jadx class set header");
}
classes[i].setParents(parents);
int count = in.readInt();
classes = new NClass[count];
for (int i = 0; i < count; i++) {
String name = readString(in);
classes[i] = new NClass(name, i);
}
for (int i = 0; i < count; i++) {
int pCount = in.readByte();
NClass[] parents = new NClass[pCount];
for (int j = 0; j < pCount; j++) {
parents[j] = classes[in.readInt()];
}
classes[i].setParents(parents);
}
} finally {
in.close();
}
}
@@ -77,15 +77,15 @@ public class ClspGraph {
return clsName;
}
NClass cls = nameMap.get(implClsName);
if (cls != null) {
if (isImplements(clsName, implClsName)) {
return implClsName;
}
Set<String> anc = getAncestors(clsName);
return searchCommonParent(anc, cls);
if (cls == null) {
LOG.debug("Missing class: {}", implClsName);
return null;
}
LOG.debug("Missing class: {}", implClsName);
return null;
if (isImplements(clsName, implClsName)) {
return implClsName;
}
Set<String> anc = getAncestors(clsName);
return searchCommonParent(anc, cls);
}
private String searchCommonParent(Set<String> anc, NClass cls) {
@@ -93,11 +93,10 @@ public class ClspGraph {
String name = p.getName();
if (anc.contains(name)) {
return name;
} else {
String r = searchCommonParent(anc, p);
if (r != null) {
return r;
}
}
String r = searchCommonParent(anc, p);
if (r != null) {
return r;
}
}
return null;
@@ -109,17 +108,17 @@ public class ClspGraph {
return result;
}
NClass cls = nameMap.get(clsName);
if (cls != null) {
result = new HashSet<String>();
addAncestorsNames(cls, result);
if (result.isEmpty()) {
result = Collections.emptySet();
}
ancestorCache.put(clsName, result);
return result;
if (cls == null) {
LOG.debug("Missing class: {}", clsName);
return Collections.emptySet();
}
LOG.debug("Missing class: {}", clsName);
return Collections.emptySet();
result = new HashSet<String>();
addAncestorsNames(cls, result);
if (result.isEmpty()) {
result = Collections.emptySet();
}
ancestorCache.put(clsName, result);
return result;
}
private void addAncestorsNames(NClass cls, Set<String> result) {
@@ -54,6 +54,9 @@ public class ConvertToClsSet {
private static void addFilesFromDirectory(File dir, List<InputFile> inputFiles) throws IOException, DecodeException {
File[] files = dir.listFiles();
if (files == null) {
return;
}
for (File file : files) {
if (file.isDirectory()) {
addFilesFromDirectory(file, inputFiles);
@@ -1,7 +1,7 @@
package jadx.core.codegen;
import jadx.core.Consts;
import jadx.core.dex.attributes.AttributeType;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttributeNode;
import jadx.core.dex.attributes.annotations.Annotation;
import jadx.core.dex.attributes.annotations.AnnotationsList;
@@ -53,7 +53,7 @@ public class AnnotationGen {
}
private void add(IAttributeNode node, CodeWriter code) {
AnnotationsList aList = (AnnotationsList) node.getAttributes().get(AttributeType.ANNOTATION_LIST);
AnnotationsList aList = node.get(AType.ANNOTATION_LIST);
if (aList == null || aList.size() == 0) {
return;
}
@@ -73,7 +73,7 @@ public class AnnotationGen {
private void formatAnnotation(CodeWriter code, Annotation a) {
code.add('@');
code.add(classGen.useClass(a.getType()));
classGen.useType(code, a.getType());
Map<String, Object> vl = a.getValues();
if (!vl.isEmpty()) {
code.add('(');
@@ -96,13 +96,13 @@ public class AnnotationGen {
@SuppressWarnings("unchecked")
public void addThrows(MethodNode mth, CodeWriter code) {
Annotation an = mth.getAttributes().getAnnotation(Consts.DALVIK_THROWS);
Annotation an = mth.getAnnotation(Consts.DALVIK_THROWS);
if (an != null) {
Object exs = an.getDefaultValue();
code.add(" throws ");
for (Iterator<ArgType> it = ((List<ArgType>) exs).iterator(); it.hasNext(); ) {
ArgType ex = it.next();
code.add(TypeGen.translate(classGen, ex));
classGen.useType(code, ex);
if (it.hasNext()) {
code.add(", ");
}
@@ -111,7 +111,7 @@ public class AnnotationGen {
}
public Object getAnnotationDefaultValue(String name) {
Annotation an = cls.getAttributes().getAnnotation(Consts.DALVIK_ANNOTATION_DEFAULT);
Annotation an = cls.getAnnotation(Consts.DALVIK_ANNOTATION_DEFAULT);
if (an != null) {
Annotation defAnnotation = (Annotation) an.getDefaultValue();
return defAnnotation.getValues().get(name);
@@ -144,11 +144,12 @@ public class AnnotationGen {
} else if (val instanceof Byte) {
code.add(TypeGen.formatByte((Byte) val));
} else if (val instanceof ArgType) {
code.add(TypeGen.translate(classGen, (ArgType) val)).add(".class");
classGen.useType(code, (ArgType) val);
code.add(".class");
} else if (val instanceof FieldInfo) {
// must be a static field
FieldInfo field = (FieldInfo) val;
code.add(InsnGen.makeStaticFieldAccess(field, classGen));
InsnGen.makeStaticFieldAccess(code, field, classGen);
} else if (val instanceof List) {
code.add('{');
Iterator<?> it = ((List) val).iterator();
@@ -1,16 +1,17 @@
package jadx.core.codegen;
import jadx.core.Consts;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.AttrNode;
import jadx.core.dex.attributes.AttributeFlag;
import jadx.core.dex.attributes.AttributeType;
import jadx.core.dex.attributes.EnumClassAttr;
import jadx.core.dex.attributes.EnumClassAttr.EnumField;
import jadx.core.dex.attributes.SourceFileAttr;
import jadx.core.dex.attributes.nodes.EnumClassAttr;
import jadx.core.dex.attributes.nodes.EnumClassAttr.EnumField;
import jadx.core.dex.attributes.nodes.SourceFileAttr;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.PrimitiveType;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.DexNode;
import jadx.core.dex.nodes.FieldNode;
@@ -43,7 +44,7 @@ public class ClassGen {
private final boolean fallback;
private final Set<ClassInfo> imports = new HashSet<ClassInfo>();
private int clsDeclLine = 0;
private int clsDeclLine;
public ClassGen(ClassNode cls, ClassGen parentClsGen, boolean fallback) {
this.cls = cls;
@@ -62,12 +63,10 @@ public class ClassGen {
addClassCode(clsBody);
CodeWriter clsCode = new CodeWriter();
if (!"".equals(cls.getPackage())) {
clsCode.add("package ").add(cls.getPackage()).add(';');
clsCode.newLine();
}
int importsCount = imports.size();
if (importsCount != 0) {
List<String> sortImports = new ArrayList<String>(importsCount);
@@ -84,16 +83,15 @@ public class ClassGen {
sortImports.clear();
imports.clear();
}
clsCode.add(clsBody);
return clsCode;
}
public void addClassCode(CodeWriter code) throws CodegenException {
if (cls.getAttributes().contains(AttributeFlag.DONT_GENERATE)) {
if (cls.contains(AFlag.DONT_GENERATE)) {
return;
}
if (cls.getAttributes().contains(AttributeFlag.INCONSISTENT_CODE)) {
if (cls.contains(AFlag.INCONSISTENT_CODE)) {
code.startLine("// jadx: inconsistent code");
}
addClassDeclaration(code);
@@ -105,7 +103,9 @@ public class ClassGen {
if (af.isInterface()) {
af = af.remove(AccessFlags.ACC_ABSTRACT);
} else if (af.isEnum()) {
af = af.remove(AccessFlags.ACC_FINAL).remove(AccessFlags.ACC_ABSTRACT);
af = af.remove(AccessFlags.ACC_FINAL)
.remove(AccessFlags.ACC_ABSTRACT)
.remove(AccessFlags.ACC_STATIC);
}
annotationGen.addForClass(clsCode);
@@ -130,7 +130,9 @@ public class ClassGen {
if (sup != null
&& !sup.getFullName().equals(Consts.CLASS_OBJECT)
&& !sup.getFullName().equals(Consts.CLASS_ENUM)) {
clsCode.add("extends ").add(useClass(sup)).add(' ');
clsCode.add("extends ");
useClass(clsCode, sup);
clsCode.add(' ');
}
if (cls.getInterfaces().size() > 0 && !af.isAnnotation()) {
@@ -141,7 +143,7 @@ public class ClassGen {
}
for (Iterator<ClassInfo> it = cls.getInterfaces().iterator(); it.hasNext(); ) {
ClassInfo interf = it.next();
clsCode.add(useClass(interf));
useClass(clsCode, interf);
if (it.hasNext()) {
clsCode.add(", ");
}
@@ -165,12 +167,20 @@ public class ClassGen {
if (i != 0) {
code.add(", ");
}
code.add(useClass(type));
if (type.isGenericType()) {
code.add(type.getObject());
} else {
useClass(code, ClassInfo.fromType(type));
}
if (list != null && !list.isEmpty()) {
code.add(" extends ");
for (Iterator<ArgType> it = list.iterator(); it.hasNext(); ) {
ArgType g = it.next();
code.add(useClass(g));
if (g.isGenericType()) {
code.add(g.getObject());
} else {
useClass(code, ClassInfo.fromType(g));
}
if (it.hasNext()) {
code.add(" & ");
}
@@ -194,35 +204,56 @@ public class ClassGen {
}
private void addInnerClasses(CodeWriter code, ClassNode cls) throws CodegenException {
for (ClassNode innerCls : cls.getInnerClasses()) {
if (innerCls.contains(AFlag.DONT_GENERATE)
|| innerCls.isAnonymous()) {
continue;
}
ClassGen inClGen = new ClassGen(innerCls, getParentGen(), fallback);
code.newLine();
inClGen.addClassCode(code);
imports.addAll(inClGen.getImports());
}
}
private boolean isInnerClassesPresents() {
for (ClassNode innerCls : cls.getInnerClasses()) {
if (!innerCls.isAnonymous()) {
ClassGen inClGen = new ClassGen(innerCls, getParentGen(), fallback);
code.newLine();
inClGen.addClassCode(code);
imports.addAll(inClGen.getImports());
return true;
}
}
return false;
}
private void addMethods(CodeWriter code) {
for (MethodNode mth : cls.getMethods()) {
if (!mth.getAttributes().contains(AttributeFlag.DONT_GENERATE)) {
try {
if (code.getLine() != clsDeclLine) {
code.newLine();
}
addMethod(code, mth);
} catch (Exception e) {
String msg = ErrorsCounter.methodError(mth, "Method generation error", e);
code.startLine("/* " + msg + CodeWriter.NL + Utils.getStackTrace(e) + " */");
}
if (mth.contains(AFlag.DONT_GENERATE)) {
continue;
}
if (code.getLine() != clsDeclLine) {
code.newLine();
}
try {
addMethod(code, mth);
} catch (Exception e) {
String msg = ErrorsCounter.methodError(mth, "Method generation error", e);
code.startLine("/* " + msg + CodeWriter.NL + Utils.getStackTrace(e) + " */");
}
}
}
private boolean isMethodsPresents() {
for (MethodNode mth : cls.getMethods()) {
if (!mth.contains(AFlag.DONT_GENERATE)) {
return true;
}
}
return false;
}
private void addMethod(CodeWriter code, MethodNode mth) throws CodegenException {
MethodGen mthGen = new MethodGen(this, mth);
if (mth.getAccessFlags().isAbstract() || mth.getAccessFlags().isNative()) {
MethodGen mthGen = new MethodGen(this, mth);
mthGen.addDefinition(code);
if (cls.getAccessFlags().isAnnotation()) {
Object def = annotationGen.getAnnotationDefaultValue(mth.getName());
@@ -233,11 +264,17 @@ public class ClassGen {
}
code.add(';');
} else {
boolean badCode = mth.getAttributes().contains(AttributeFlag.INCONSISTENT_CODE);
boolean badCode = mth.contains(AFlag.INCONSISTENT_CODE);
if (badCode) {
code.startLine("/* JADX WARNING: inconsistent code. */");
code.startLine("/* Code decompiled incorrectly, please refer to instructions dump. */");
LOG.error(ErrorsCounter.formatErrorMsg(mth, " Inconsistent code"));
ErrorsCounter.methodError(mth, "Inconsistent code");
}
MethodGen mthGen;
if (badCode || mth.contains(AType.JADX_ERROR)) {
mthGen = MethodGen.getFallbackMethodGen(mth);
} else {
mthGen = new MethodGen(this, mth);
}
if (mthGen.addDefinition(code)) {
code.add(' ');
@@ -254,15 +291,15 @@ public class ClassGen {
private void addFields(CodeWriter code) throws CodegenException {
addEnumFields(code);
for (FieldNode f : cls.getFields()) {
if (f.getAttributes().contains(AttributeFlag.DONT_GENERATE)) {
if (f.contains(AFlag.DONT_GENERATE)) {
continue;
}
annotationGen.addForField(code, f);
code.startLine(f.getAccessFlags().makeString());
code.add(TypeGen.translate(this, f.getType()));
useType(code, f.getType());
code.add(' ');
code.add(f.getName());
FieldValueAttr fv = (FieldValueAttr) f.getAttributes().get(AttributeType.FIELD_VALUE);
FieldValueAttr fv = f.get(AType.FIELD_VALUE);
if (fv != null) {
code.add(" = ");
if (fv.getValue() == null) {
@@ -276,82 +313,104 @@ public class ClassGen {
}
}
private void addEnumFields(CodeWriter code) throws CodegenException {
EnumClassAttr enumFields = (EnumClassAttr) cls.getAttributes().get(AttributeType.ENUM_CLASS);
if (enumFields != null) {
InsnGen igen = null;
for (Iterator<EnumField> it = enumFields.getFields().iterator(); it.hasNext(); ) {
EnumField f = it.next();
code.startLine(f.getName());
if (f.getArgs().size() != 0) {
code.add('(');
for (Iterator<InsnArg> aIt = f.getArgs().iterator(); aIt.hasNext(); ) {
InsnArg arg = aIt.next();
if (igen == null) {
// don't init mth gen if this is simple enum
MethodGen mthGen = new MethodGen(this, enumFields.getStaticMethod());
igen = new InsnGen(mthGen, false);
}
igen.addArg(code, arg);
if (aIt.hasNext()) {
code.add(", ");
}
}
code.add(')');
}
if (f.getCls() != null) {
new ClassGen(f.getCls(), this, fallback).addClassBody(code);
}
if (it.hasNext()) {
code.add(',');
}
private boolean isFieldsPresents() {
for (FieldNode field : cls.getFields()) {
if (!field.contains(AFlag.DONT_GENERATE)) {
return true;
}
}
return false;
}
private void addEnumFields(CodeWriter code) throws CodegenException {
EnumClassAttr enumFields = cls.get(AType.ENUM_CLASS);
if (enumFields == null) {
return;
}
InsnGen igen = null;
for (Iterator<EnumField> it = enumFields.getFields().iterator(); it.hasNext(); ) {
EnumField f = it.next();
code.startLine(f.getName());
if (f.getArgs().size() != 0) {
code.add('(');
for (Iterator<InsnArg> aIt = f.getArgs().iterator(); aIt.hasNext(); ) {
InsnArg arg = aIt.next();
if (igen == null) {
// don't init mth gen if this is simple enum
MethodGen mthGen = new MethodGen(this, enumFields.getStaticMethod());
igen = new InsnGen(mthGen, false);
}
igen.addArg(code, arg);
if (aIt.hasNext()) {
code.add(", ");
}
}
code.add(')');
}
if (f.getCls() != null) {
code.add(' ');
new ClassGen(f.getCls(), this, fallback).addClassBody(code);
}
if (it.hasNext()) {
code.add(',');
}
}
if (isMethodsPresents() || isFieldsPresents() || isInnerClassesPresents()) {
if (enumFields.getFields().isEmpty()) {
code.startLine();
}
code.add(';');
code.newLine();
}
}
public String useClass(ArgType clsType) {
if (clsType.isGenericType()) {
return clsType.getObject();
}
return useClass(ClassInfo.fromType(clsType));
}
public String useClass(ClassInfo classInfo) {
String baseClass = useClassInternal(cls.getClassInfo(), classInfo);
ArgType type = classInfo.getType();
ArgType[] generics = type.getGenericTypes();
if (generics == null) {
return baseClass;
}
StringBuilder sb = new StringBuilder();
sb.append(baseClass);
sb.append('<');
int len = generics.length;
for (int i = 0; i < len; i++) {
if (i != 0) {
sb.append(", ");
}
ArgType gt = generics[i];
ArgType wt = gt.getWildcardType();
if (wt != null) {
sb.append('?');
int bounds = gt.getWildcardBounds();
if (bounds != 0) {
sb.append(bounds == -1 ? " super " : " extends ");
sb.append(TypeGen.translate(this, wt));
}
public void useType(CodeWriter code, ArgType type) {
PrimitiveType stype = type.getPrimitiveType();
if (stype == null) {
code.add(type.toString());
} else if (stype == PrimitiveType.OBJECT) {
if (type.isGenericType()) {
code.add(type.getObject());
} else {
sb.append(TypeGen.translate(this, gt));
useClass(code, ClassInfo.fromType(type));
}
} else if (stype == PrimitiveType.ARRAY) {
useType(code, type.getArrayElement());
code.add("[]");
} else {
code.add(stype.getLongName());
}
}
public void useClass(CodeWriter code, ClassInfo classInfo) {
ClassNode classNode = cls.dex().resolveClass(classInfo);
if (classNode != null) {
code.attachAnnotation(classNode);
}
String baseClass = useClassInternal(cls.getClassInfo(), classInfo);
ArgType[] generics = classInfo.getType().getGenericTypes();
code.add(baseClass);
if (generics != null) {
code.add('<');
int len = generics.length;
for (int i = 0; i < len; i++) {
if (i != 0) {
code.add(", ");
}
ArgType gt = generics[i];
ArgType wt = gt.getWildcardType();
if (wt != null) {
code.add('?');
int bounds = gt.getWildcardBounds();
if (bounds != 0) {
code.add(bounds == -1 ? " super " : " extends ");
useType(code, wt);
}
} else {
useType(code, gt);
}
}
code.add('>');
}
sb.append('>');
return sb.toString();
}
private String useClassInternal(ClassInfo useCls, ClassInfo classInfo) {
@@ -441,7 +500,7 @@ public class ClassGen {
}
private void insertSourceFileInfo(CodeWriter code, AttrNode node) {
SourceFileAttr sourceFileAttr = (SourceFileAttr) node.getAttributes().get(AttributeType.SOURCE_FILE);
SourceFileAttr sourceFileAttr = node.get(AType.SOURCE_FILE);
if (sourceFileAttr != null) {
code.startLine("// compiled from: ").add(sourceFileAttr.getFileName());
}
@@ -1,7 +1,7 @@
package jadx.core.codegen;
import jadx.api.CodePosition;
import jadx.core.dex.attributes.LineAttrNode;
import jadx.core.dex.attributes.nodes.LineAttrNode;
import jadx.core.utils.Utils;
import java.io.File;
@@ -10,6 +10,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -37,17 +38,13 @@ public class CodeWriter {
private int line = 1;
private int offset = 0;
private Map<CodePosition, Object> annotations = Collections.emptyMap();
private Map<Integer, Integer> lineMap = Collections.emptyMap();
public CodeWriter() {
this.indent = 0;
this.indentStr = "";
}
public CodeWriter(int indent) {
this.indent = indent;
updateIndent();
}
public CodeWriter startLine() {
addLine();
addLineIndent();
@@ -68,11 +65,6 @@ public class CodeWriter {
return this;
}
public CodeWriter add(Object obj) {
add(obj.toString());
return this;
}
public CodeWriter add(String str) {
buf.append(str);
offset += str.length();
@@ -91,6 +83,9 @@ public class CodeWriter {
CodePosition pos = entry.getKey();
attachAnnotation(entry.getValue(), new CodePosition(line + pos.getLine(), pos.getOffset()));
}
for (Map.Entry<Integer, Integer> entry : code.lineMap.entrySet()) {
attachSourceLine(line + entry.getKey(), entry.getValue());
}
line += code.line;
offset = code.offset;
buf.append(code);
@@ -102,6 +97,11 @@ public class CodeWriter {
return this;
}
public CodeWriter addIndent() {
add(INDENT);
return this;
}
private void addLine() {
buf.append(NL);
line++;
@@ -114,11 +114,6 @@ public class CodeWriter {
return this;
}
public CodeWriter addIndent() {
add(INDENT);
return this;
}
private void updateIndent() {
int curIndent = indent;
if (curIndent < INDENT_CACHE.length) {
@@ -189,6 +184,24 @@ public class CodeWriter {
return annotations;
}
public void attachSourceLine(int sourceLine) {
if (sourceLine == 0) {
return;
}
attachSourceLine(line, sourceLine);
}
private void attachSourceLine(int decompiledLine, int sourceLine) {
if (lineMap.isEmpty()) {
lineMap = new TreeMap<Integer, Integer>();
}
lineMap.put(decompiledLine, sourceLine);
}
public Map<Integer, Integer> getLineMapping() {
return lineMap;
}
public void finish() {
buf.trimToSize();
Iterator<Map.Entry<CodePosition, Object>> it = annotations.entrySet().iterator();
@@ -206,9 +219,8 @@ public class CodeWriter {
private static String removeFirstEmptyLine(String str) {
if (str.startsWith(NL)) {
return str.substring(NL.length());
} else {
return str;
}
return str;
}
public int length() {
@@ -10,6 +10,7 @@ import jadx.core.dex.instructions.args.LiteralArg;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.regions.Compare;
import jadx.core.dex.regions.IfCondition;
import jadx.core.dex.regions.IfCondition.Mode;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.exceptions.CodegenException;
import jadx.core.utils.exceptions.JadxRuntimeException;
@@ -75,7 +76,7 @@ public class ConditionGen extends InsnGen {
} else if (op == IfOp.NE) {
// != true
code.add('!');
boolean wrap = isWrapNeeded(firstArg);
boolean wrap = isArgWrapNeeded(firstArg);
if (wrap) {
code.add('(');
}
@@ -88,9 +89,9 @@ public class ConditionGen extends InsnGen {
LOG.warn(ErrorsCounter.formatErrorMsg(mth, "Unsupported boolean condition " + op.getSymbol()));
}
addArg(code, firstArg, isWrapNeeded(firstArg));
addArg(code, firstArg, isArgWrapNeeded(firstArg));
code.add(' ').add(op.getSymbol()).add(' ');
addArg(code, secondArg, isWrapNeeded(secondArg));
addArg(code, secondArg, isArgWrapNeeded(secondArg));
}
private void addNot(CodeWriter code, IfCondition condition) throws CodegenException {
@@ -99,7 +100,7 @@ public class ConditionGen extends InsnGen {
}
private void addAndOr(CodeWriter code, IfCondition condition) throws CodegenException {
String mode = condition.getMode() == IfCondition.Mode.AND ? " && " : " || ";
String mode = condition.getMode() == Mode.AND ? " && " : " || ";
Iterator<IfCondition> it = condition.getArgs().iterator();
while (it.hasNext()) {
wrap(code, it.next());
@@ -110,15 +111,16 @@ public class ConditionGen extends InsnGen {
}
private boolean isWrapNeeded(IfCondition condition) {
return !condition.isCompare();
return !condition.isCompare() && condition.getMode() != Mode.NOT;
}
private static boolean isWrapNeeded(InsnArg arg) {
private static boolean isArgWrapNeeded(InsnArg arg) {
if (!arg.isInsnWrap()) {
return false;
}
InsnNode insn = ((InsnWrapArg) arg).getWrapInsn();
if (insn.getType() == InsnType.ARITH) {
InsnType insnType = insn.getType();
if (insnType == InsnType.ARITH) {
switch (((ArithNode) insn).getOp()) {
case ADD:
case SUB:
@@ -127,8 +129,18 @@ public class ConditionGen extends InsnGen {
case REM:
return false;
}
} else if (insn.getType() == InsnType.INVOKE) {
return false;
} else {
switch (insnType) {
case INVOKE:
case SGET:
case IGET:
case AGET:
case CONST:
case ARRAY_LENGTH:
return false;
default:
return true;
}
}
return true;
}
@@ -1,10 +1,9 @@
package jadx.core.codegen;
import jadx.core.dex.attributes.AttributeFlag;
import jadx.core.dex.attributes.AttributeType;
import jadx.core.dex.attributes.FieldReplaceAttr;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.attributes.MethodInlineAttr;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
import jadx.core.dex.attributes.nodes.MethodInlineAttr;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.info.MethodInfo;
@@ -59,7 +58,7 @@ public class InsnGen {
protected final RootNode root;
protected final boolean fallback;
private static enum Flags {
private enum Flags {
BODY_ONLY,
BODY_ONLY_NOWRAP,
}
@@ -89,7 +88,7 @@ public class InsnGen {
public void addArg(CodeWriter code, InsnArg arg, boolean wrap) throws CodegenException {
if (arg.isRegister()) {
code.add(mgen.makeArgName((RegisterArg) arg));
code.add(mgen.getNameGen().useArg((RegisterArg) arg));
} else if (arg.isLiteral()) {
code.add(lit((LiteralArg) arg));
} else if (arg.isInsnWrap()) {
@@ -100,11 +99,9 @@ public class InsnGen {
} else if (arg.isField()) {
FieldArg f = (FieldArg) arg;
if (f.isStatic()) {
code.add(staticField(f.getField()));
staticField(code, f.getField());
} else {
RegisterArg regArg = new RegisterArg(f.getRegNum());
regArg.replaceTypedVar(f);
instanceField(code, f.getField(), regArg);
instanceField(code, f.getField(), f.getInstanceArg());
}
} else {
throw new CodegenException("Unknown arg type " + arg);
@@ -113,7 +110,7 @@ public class InsnGen {
public void assignVar(CodeWriter code, InsnNode insn) throws CodegenException {
RegisterArg arg = insn.getResult();
if (insn.getAttributes().contains(AttributeFlag.DECLARE_VAR)) {
if (insn.contains(AFlag.DECLARE_VAR)) {
declareVar(code, arg);
} else {
addArg(code, arg, false);
@@ -121,9 +118,9 @@ public class InsnGen {
}
public void declareVar(CodeWriter code, RegisterArg arg) {
code.add(useType(arg.getType()));
useType(code, arg.getType());
code.add(' ');
code.add(mgen.assignArg(arg));
code.add(mgen.getNameGen().assignArg(arg));
}
private static String lit(LiteralArg arg) {
@@ -133,42 +130,56 @@ public class InsnGen {
private void instanceField(CodeWriter code, FieldInfo field, InsnArg arg) throws CodegenException {
FieldNode fieldNode = mth.getParentClass().searchField(field);
if (fieldNode != null) {
FieldReplaceAttr replace = (FieldReplaceAttr) fieldNode.getAttributes().get(AttributeType.FIELD_REPLACE);
FieldReplaceAttr replace = fieldNode.get(AType.FIELD_REPLACE);
if (replace != null) {
FieldInfo info = replace.getFieldInfo();
if (replace.isOuterClass()) {
code.add(useClass(info.getDeclClass())).add(".this");
useClass(code, info.getDeclClass());
code.add(".this");
}
return;
}
}
addArgDot(code, arg);
fieldNode = mth.dex().resolveField(field);
if (fieldNode != null) {
code.attachAnnotation(fieldNode);
}
code.add(field.getName());
}
public static String makeStaticFieldAccess(FieldInfo field, ClassGen clsGen) {
public static void makeStaticFieldAccess(CodeWriter code, FieldInfo field, ClassGen clsGen) {
ClassInfo declClass = field.getDeclClass();
if (clsGen.getClassNode().getFullName().startsWith(declClass.getFullName())) {
return field.getName();
boolean fieldFromThisClass = clsGen.getClassNode().getFullName().startsWith(declClass.getFullName());
if (!fieldFromThisClass) {
// Android specific resources class handler
ClassInfo parentClass = declClass.getParentClass();
if (parentClass != null && parentClass.getShortName().equals("R")) {
clsGen.useClass(code, parentClass);
code.add('.');
code.add(declClass.getShortName());
} else {
clsGen.useClass(code, declClass);
}
code.add('.');
}
// Android specific resources class handler
ClassInfo parentClass = declClass.getParentClass();
if (parentClass != null && parentClass.getShortName().equals("R")) {
return clsGen.useClass(parentClass) + "." + declClass.getShortName() + "." + field.getName();
FieldNode fieldNode = clsGen.getClassNode().dex().resolveField(field);
if (fieldNode != null) {
code.attachAnnotation(fieldNode);
}
return clsGen.useClass(declClass) + '.' + field.getName();
code.add(field.getName());
}
protected String staticField(FieldInfo field) {
return makeStaticFieldAccess(field, mgen.getClassGen());
protected void staticField(CodeWriter code, FieldInfo field) {
makeStaticFieldAccess(code, field, mgen.getClassGen());
}
public String useClass(ClassInfo cls) {
return mgen.getClassGen().useClass(cls);
public void useClass(CodeWriter code, ClassInfo cls) {
mgen.getClassGen().useClass(code, cls);
}
private String useType(ArgType type) {
return TypeGen.translate(mgen.getClassGen(), type);
private void useType(CodeWriter code, ArgType type) {
mgen.getClassGen().useType(code, type);
}
public boolean makeInsn(InsnNode insn, CodeWriter code) throws CodegenException {
@@ -187,7 +198,7 @@ public class InsnGen {
} else {
code.startLine();
if (insn.getSourceLine() != 0) {
code.attachAnnotation(insn.getSourceLine());
code.attachSourceLine(insn.getSourceLine());
}
if (insn.getResult() != null && insn.getType() != InsnType.ARITH_ONEARG) {
assignVar(code, insn);
@@ -211,7 +222,8 @@ public class InsnGen {
case CONST_CLASS:
ArgType clsType = ((ConstClassNode) insn).getClsType();
code.add(useType(clsType)).add(".class");
useType(code, clsType);
code.add(".class");
break;
case CONST:
@@ -230,7 +242,7 @@ public class InsnGen {
code.add('(');
}
code.add('(');
code.add(useType((ArgType) ((IndexInsnNode) insn).getIndex()));
useType(code, (ArgType) ((IndexInsnNode) insn).getIndex());
code.add(") ");
addArg(code, insn.getArg(0), true);
if (wrap) {
@@ -244,7 +256,7 @@ public class InsnGen {
break;
case ARITH_ONEARG:
makeArithOneArg((ArithNode) insn, code, state);
makeArithOneArg((ArithNode) insn, code);
break;
case NEG: {
@@ -302,14 +314,14 @@ public class InsnGen {
}
addArg(code, insn.getArg(0));
code.add(" instanceof ");
code.add(useType((ArgType) ((IndexInsnNode) insn).getIndex()));
useType(code, (ArgType) ((IndexInsnNode) insn).getIndex());
if (wrap) {
code.add(')');
}
break;
}
case CONSTRUCTOR:
makeConstructor((ConstructorInsn) insn, code, state);
makeConstructor((ConstructorInsn) insn, code);
break;
case INVOKE:
@@ -318,7 +330,8 @@ public class InsnGen {
case NEW_ARRAY: {
ArgType arrayType = insn.getResult().getType();
code.add("new ").add(useType(arrayType.getArrayRootElement()));
code.add("new ");
useType(code, arrayType.getArrayRootElement());
code.add('[');
addArg(code, insn.getArg(0));
code.add(']');
@@ -371,11 +384,12 @@ public class InsnGen {
}
case SGET:
code.add(staticField((FieldInfo) ((IndexInsnNode) insn).getIndex()));
staticField(code, (FieldInfo) ((IndexInsnNode) insn).getIndex());
break;
case SPUT:
FieldInfo field = (FieldInfo) ((IndexInsnNode) insn).getIndex();
code.add(staticField(field)).add(" = ");
staticField(code, field);
code.add(" = ");
addArg(code, insn.getArg(0), false);
break;
@@ -427,6 +441,9 @@ public class InsnGen {
addArg(code, insn.getArg(0));
break;
case PHI:
break;
/* fallback mode instructions */
case IF:
assert isFallback() : "if insn in not fallback mode";
@@ -452,7 +469,8 @@ public class InsnGen {
code.add(") {");
code.incIndent();
for (int i = 0; i < sw.getCasesCount(); i++) {
code.startLine("case ").add(sw.getKeys()[i]).add(": goto ");
String key = sw.getKeys()[i].toString();
code.startLine("case ").add(key).add(": goto ");
code.add(MethodGen.getLabelName(sw.getTargets()[i])).add(';');
}
code.startLine("default: goto ");
@@ -474,10 +492,11 @@ public class InsnGen {
private void filledNewArray(InsnNode insn, CodeWriter code) throws CodegenException {
int c = insn.getArgsCount();
code.add("new ").add(useType(insn.getResult().getType()));
code.add("new ");
useType(code, insn.getResult().getType());
code.add('{');
for (int i = 0; i < c; i++) {
addArg(code, insn.getArg(i));
addArg(code, insn.getArg(i), false);
if (i + 1 < c) {
code.add(", ");
}
@@ -539,13 +558,15 @@ public class InsnGen {
}
int len = str.length();
str.delete(len - 2, len);
code.add("new ").add(useType(elType)).add("[]{").add(str.toString()).add('}');
code.add("new ");
useType(code, elType);
code.add("[]{").add(str.toString()).add('}');
}
private void makeConstructor(ConstructorInsn insn, CodeWriter code, EnumSet<Flags> state)
private void makeConstructor(ConstructorInsn insn, CodeWriter code)
throws CodegenException {
ClassNode cls = mth.dex().resolveClass(insn.getClassType());
if (cls != null && cls.isAnonymous()) {
if (cls != null && cls.isAnonymous() && !fallback) {
// anonymous class construction
ClassInfo parent;
if (cls.getInterfaces().size() == 1) {
@@ -553,14 +574,22 @@ public class InsnGen {
} else {
parent = cls.getSuperClass();
}
cls.getAttributes().add(AttributeFlag.DONT_GENERATE);
cls.add(AFlag.DONT_GENERATE);
MethodNode defCtr = cls.getDefaultConstructor();
if (RegionUtils.notEmpty(defCtr.getRegion())) {
defCtr.getAttributes().add(AttributeFlag.ANONYMOUS_CONSTRUCTOR);
} else {
defCtr.getAttributes().add(AttributeFlag.DONT_GENERATE);
if (defCtr != null) {
if (RegionUtils.notEmpty(defCtr.getRegion())) {
defCtr.add(AFlag.ANONYMOUS_CONSTRUCTOR);
} else {
defCtr.add(AFlag.DONT_GENERATE);
}
}
code.add("new ").add(parent == null ? "Object" : useClass(parent)).add("() ");
code.add("new ");
if (parent == null) {
code.add("Object");
} else {
useClass(code, parent);
}
code.add("() ");
new ClassGen(cls, mgen.getClassGen().getParentGen(), fallback).addClassBody(code);
return;
}
@@ -572,7 +601,8 @@ public class InsnGen {
} else if (insn.isThis()) {
code.add("this");
} else {
code.add("new ").add(useClass(insn.getClassType()));
code.add("new ");
useClass(code, insn.getClassType());
}
generateArguments(code, insn, 0, mth.dex().resolveMethod(insn.getCallMth()));
}
@@ -582,9 +612,7 @@ public class InsnGen {
// inline method
MethodNode callMthNode = mth.dex().resolveMethod(callMth);
if (callMthNode != null
&& callMthNode.getAttributes().contains(AttributeType.METHOD_INLINE)) {
inlineMethod(callMthNode, insn, code);
if (callMthNode != null && inlineMethod(callMthNode, insn, code)) {
return;
}
@@ -612,7 +640,8 @@ public class InsnGen {
ClassInfo insnCls = mth.getParentClass().getClassInfo();
ClassInfo declClass = callMth.getDeclClass();
if (!insnCls.equals(declClass)) {
code.add(useClass(declClass)).add('.');
useClass(code, declClass);
code.add('.');
}
break;
}
@@ -624,7 +653,7 @@ public class InsnGen {
}
private void generateArguments(CodeWriter code, InsnNode insn, int k, MethodNode callMth) throws CodegenException {
if (callMth != null && callMth.getAttributes().contains(AttributeFlag.SKIP_FIRST_ARG)) {
if (callMth != null && callMth.contains(AFlag.SKIP_FIRST_ARG)) {
k++;
}
int argsCount = insn.getArgsCount();
@@ -637,7 +666,9 @@ public class InsnGen {
InsnArg arg = insn.getArg(i);
ArgType origType = originalType.get(origPos);
if (!arg.getType().equals(origType)) {
code.add('(').add(useType(origType)).add(')');
code.add('(');
useType(code, origType);
code.add(')');
addArg(code, arg, true);
} else {
addArg(code, arg, false);
@@ -661,9 +692,12 @@ public class InsnGen {
}
}
private void inlineMethod(MethodNode callMthNode, InvokeNode insn, CodeWriter code) throws CodegenException {
IAttribute mia = callMthNode.getAttributes().get(AttributeType.METHOD_INLINE);
InsnNode inl = ((MethodInlineAttr) mia).getInsn();
private boolean inlineMethod(MethodNode callMthNode, InvokeNode insn, CodeWriter code) throws CodegenException {
MethodInlineAttr mia = callMthNode.get(AType.METHOD_INLINE);
if (mia == null) {
return false;
}
InsnNode inl = mia.getInsn();
if (callMthNode.getMethodInfo().getArgumentsTypes().isEmpty()) {
makeInsn(inl, code, Flags.BODY_ONLY);
} else {
@@ -680,12 +714,13 @@ public class InsnGen {
inl.getRegisterArgs(inlArgs);
Map<RegisterArg, InsnArg> toRevert = new HashMap<RegisterArg, InsnArg>();
for (RegisterArg r : inlArgs) {
if (r.getRegNum() >= regs.length) {
LOG.warn("Unknown register number {} in method call: {}, {}", r, callMthNode, mth);
int regNum = r.getRegNum();
if (regNum >= regs.length) {
LOG.warn("Unknown register number {} in method call: {} from {}", r, callMthNode, mth);
} else {
InsnArg repl = regs[r.getRegNum()];
InsnArg repl = regs[regNum];
if (repl == null) {
LOG.warn("Not passed register {} in method call: {}, {}", r, callMthNode, mth);
LOG.warn("Not passed register {} in method call: {} from {}", r, callMthNode, mth);
} else {
inl.replaceArg(r, repl);
toRevert.put(r, repl);
@@ -693,11 +728,12 @@ public class InsnGen {
}
}
makeInsn(inl, code, Flags.BODY_ONLY);
// revert changes
// revert changes in 'MethodInlineAttr'
for (Map.Entry<RegisterArg, InsnArg> e : toRevert.entrySet()) {
inl.replaceArg(e.getValue(), e.getKey());
}
}
return true;
}
private void makeTernary(TernaryInsn insn, CodeWriter code, EnumSet<Flags> state) throws CodegenException {
@@ -724,8 +760,7 @@ public class InsnGen {
private void makeArith(ArithNode insn, CodeWriter code, EnumSet<Flags> state) throws CodegenException {
// wrap insn in brackets for save correct operation order
boolean wrap = state.contains(Flags.BODY_ONLY)
&& !insn.getAttributes().contains(AttributeFlag.DONT_WRAP);
boolean wrap = state.contains(Flags.BODY_ONLY) && !insn.contains(AFlag.DONT_WRAP);
if (wrap) {
code.add('(');
}
@@ -739,7 +774,7 @@ public class InsnGen {
}
}
private void makeArithOneArg(ArithNode insn, CodeWriter code, EnumSet<Flags> state) throws CodegenException {
private void makeArithOneArg(ArithNode insn, CodeWriter code) throws CodegenException {
ArithOp op = insn.getOp();
InsnArg arg = insn.getArg(0);
// "++" or "--"
@@ -1,18 +1,14 @@
package jadx.core.codegen;
import jadx.core.Consts;
import jadx.core.dex.attributes.AttributeFlag;
import jadx.core.dex.attributes.AttributeType;
import jadx.core.dex.attributes.AttributesList;
import jadx.core.dex.attributes.JadxErrorAttr;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.annotations.MethodParameters;
import jadx.core.dex.attributes.nodes.JadxErrorAttr;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.NamedArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.regions.Region;
import jadx.core.dex.trycatch.CatchAttr;
import jadx.core.dex.visitors.DepthTraversal;
import jadx.core.dex.visitors.FallbackModeVisitor;
@@ -22,10 +18,8 @@ import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.CodegenException;
import jadx.core.utils.exceptions.DecodeException;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -37,27 +31,24 @@ public class MethodGen {
private final MethodNode mth;
private final ClassGen classGen;
private final boolean fallback;
private final AnnotationGen annotationGen;
private final Set<String> varNames = new HashSet<String>();
private final NameGen nameGen;
public MethodGen(ClassGen classGen, MethodNode mth) {
this.mth = mth;
this.classGen = classGen;
this.fallback = classGen.isFallbackMode();
this.annotationGen = classGen.getAnnotationGen();
List<RegisterArg> args = mth.getArguments(true);
for (RegisterArg arg : args) {
varNames.add(makeArgName(arg));
}
this.nameGen = new NameGen(classGen.isFallbackMode());
}
public ClassGen getClassGen() {
return classGen;
}
public NameGen getNameGen() {
return nameGen;
}
public MethodNode getMethodNode() {
return mth;
}
@@ -68,7 +59,7 @@ public class MethodGen {
code.attachDefinition(mth);
return true;
}
if (mth.getAttributes().contains(AttributeFlag.ANONYMOUS_CONSTRUCTOR)) {
if (mth.contains(AFlag.ANONYMOUS_CONSTRUCTOR)) {
// don't add method name and arguments
code.startLine();
code.attachDefinition(mth);
@@ -78,15 +69,17 @@ public class MethodGen {
AccessInfo clsAccFlags = mth.getParentClass().getAccessFlags();
AccessInfo ai = mth.getAccessFlags();
// don't add 'abstract' to methods in interface
// don't add 'abstract' and 'public' to methods in interface
if (clsAccFlags.isInterface()) {
ai = ai.remove(AccessFlags.ACC_ABSTRACT);
ai = ai.remove(AccessFlags.ACC_PUBLIC);
}
// don't add 'public' for annotations
if (clsAccFlags.isAnnotation()) {
ai = ai.remove(AccessFlags.ACC_PUBLIC);
}
code.startLine(ai.makeString());
code.attachSourceLine(mth.getSourceLine());
if (classGen.addGenericMap(code, mth.getGenericMap())) {
code.add(' ');
@@ -94,7 +87,7 @@ public class MethodGen {
if (mth.getAccessFlags().isConstructor()) {
code.add(classGen.getClassNode().getShortName()); // constructor
} else {
code.add(TypeGen.translate(classGen, mth.getReturnType()));
classGen.useType(code, mth.getReturnType());
code.add(' ');
code.add(mth.getName());
}
@@ -102,7 +95,7 @@ public class MethodGen {
List<RegisterArg> args = mth.getArguments(false);
if (mth.getMethodInfo().isConstructor()
&& mth.getParentClass().getAttributes().contains(AttributeType.ENUM_CLASS)) {
&& mth.getParentClass().contains(AType.ENUM_CLASS)) {
if (args.size() == 2) {
args.clear();
} else if (args.size() > 2) {
@@ -123,9 +116,7 @@ public class MethodGen {
}
private void addMethodArguments(CodeWriter argsCode, List<RegisterArg> args) {
MethodParameters paramsAnnotation =
(MethodParameters) mth.getAttributes().get(AttributeType.ANNOTATION_MTH_PARAMETERS);
MethodParameters paramsAnnotation = mth.get(AType.ANNOTATION_MTH_PARAMETERS);
int i = 0;
for (Iterator<RegisterArg> it = args.iterator(); it.hasNext(); ) {
RegisterArg arg = it.next();
@@ -139,17 +130,17 @@ public class MethodGen {
ArgType type = arg.getType();
if (type.isArray()) {
ArgType elType = type.getArrayElement();
argsCode.add(TypeGen.translate(classGen, elType));
argsCode.add(" ...");
classGen.useType(argsCode, elType);
argsCode.add("...");
} else {
LOG.warn(ErrorsCounter.formatErrorMsg(mth, "Last argument in varargs method not array"));
argsCode.add(TypeGen.translate(classGen, arg.getType()));
classGen.useType(argsCode, arg.getType());
}
} else {
argsCode.add(TypeGen.translate(classGen, arg.getType()));
classGen.useType(argsCode, arg.getType());
}
argsCode.add(' ');
argsCode.add(makeArgName(arg));
argsCode.add(nameGen.assignArg(arg));
i++;
if (it.hasNext()) {
@@ -158,117 +149,34 @@ public class MethodGen {
}
}
/**
* Make variable name for register,
* Name contains register number and
* variable type or name (if debug info available)
*/
public String makeArgName(RegisterArg arg) {
String name = arg.getTypedVar().getName();
String base = "r" + arg.getRegNum();
if (fallback) {
if (name != null) {
return base + "_" + name;
}
return base;
} else {
if (name != null) {
if (Consts.DEBUG) {
return base + "_" + name;
}
return name;
} else {
ArgType type = arg.getType();
if (type.isPrimitive()) {
return base + type.getPrimitiveType().getShortName().toLowerCase();
} else {
return base + "_" + Utils.escape(TypeGen.translate(classGen, arg.getType()));
}
}
}
}
/**
* Put variable declaration and return variable name (used for assignments)
*
* @param arg register variable
* @return variable name
*/
public String assignArg(RegisterArg arg) {
String name = makeArgName(arg);
if (varNames.add(name) || fallback) {
return name;
}
name = getUniqVarName(name);
arg.getTypedVar().setName(name);
return name;
}
public String assignNamedArg(NamedArg arg) {
String name = arg.getName();
if (varNames.add(name) || fallback) {
return name;
}
name = getUniqVarName(name);
arg.setName(name);
return name;
}
private String getUniqVarName(String name) {
String r;
int i = 2;
do {
r = name + "_" + i;
i++;
} while (varNames.contains(r));
varNames.add(r);
return r;
}
public void addInstructions(CodeWriter code) throws CodegenException {
if (mth.getAttributes().contains(AttributeType.JADX_ERROR)) {
code.startLine("throw new UnsupportedOperationException(\"Method not decompiled: ");
code.add(mth.toString());
code.add("\");");
if (mth.contains(AType.JADX_ERROR)
|| mth.contains(AFlag.INCONSISTENT_CODE)
|| mth.getRegion() == null) {
code.startLine("throw new UnsupportedOperationException(\"Method not decompiled: ")
.add(mth.toString())
.add("\");");
JadxErrorAttr err = (JadxErrorAttr) mth.getAttributes().get(AttributeType.JADX_ERROR);
code.startLine("/* JADX: method processing error */");
Throwable cause = err.getCause();
if (cause != null) {
code.newLine();
code.add("/*");
code.startLine("Error: ").add(Utils.getStackTrace(cause));
code.add("*/");
if (mth.contains(AType.JADX_ERROR)) {
JadxErrorAttr err = mth.get(AType.JADX_ERROR);
code.startLine("/* JADX: method processing error */");
Throwable cause = err.getCause();
if (cause != null) {
code.newLine();
code.add("/*");
code.startLine("Error: ").add(Utils.getStackTrace(cause));
code.add("*/");
}
}
makeMethodDump(code);
} else if (mth.getAttributes().contains(AttributeFlag.INCONSISTENT_CODE)) {
code.startLine("/*");
addFallbackMethodCode(code);
code.startLine("*/");
code.newLine();
} else {
Region startRegion = mth.getRegion();
if (startRegion != null) {
(new RegionGen(this)).makeRegion(code, startRegion);
} else {
addFallbackMethodCode(code);
}
RegionGen regionGen = new RegionGen(this);
regionGen.makeRegion(code, mth.getRegion());
}
}
private void makeMethodDump(CodeWriter code) {
code.startLine("/*");
getFallbackMethodGen(mth).addDefinition(code);
code.add(" {");
code.incIndent();
addFallbackMethodCode(code);
code.decIndent();
code.startLine('}');
code.startLine("*/");
}
public void addFallbackMethodCode(CodeWriter code) {
if (mth.getInstructions() == null) {
// load original instructions
@@ -281,24 +189,26 @@ public class MethodGen {
return;
}
}
List<InsnNode> insns = mth.getInstructions();
if (insns == null) {
InsnNode[] insnArr = mth.getInstructions();
if (insnArr == null) {
code.startLine("// Can't load method instructions.");
return;
}
if (mth.getThisArg() != null) {
code.startLine(getFallbackMethodGen(mth).makeArgName(mth.getThisArg())).add(" = this;");
code.startLine(nameGen.useArg(mth.getThisArg())).add(" = this;");
}
addFallbackInsns(code, mth, insns, true);
addFallbackInsns(code, mth, insnArr, true);
}
public static void addFallbackInsns(CodeWriter code, MethodNode mth, List<InsnNode> insns, boolean addLabels) {
public static void addFallbackInsns(CodeWriter code, MethodNode mth, InsnNode[] insnArr, boolean addLabels) {
InsnGen insnGen = new InsnGen(getFallbackMethodGen(mth), true);
for (InsnNode insn : insns) {
AttributesList attrs = insn.getAttributes();
for (InsnNode insn : insnArr) {
if (insn == null) {
continue;
}
if (addLabels) {
if (attrs.contains(AttributeType.JUMP)
|| attrs.contains(AttributeType.EXC_HANDLER)) {
if (insn.contains(AType.JUMP)
|| insn.contains(AType.EXC_HANDLER)) {
code.decIndent();
code.startLine(getLabelName(insn.getOffset()) + ":");
code.incIndent();
@@ -306,12 +216,13 @@ public class MethodGen {
}
try {
if (insnGen.makeInsn(insn, code)) {
CatchAttr catchAttr = (CatchAttr) attrs.get(AttributeType.CATCH_BLOCK);
CatchAttr catchAttr = insn.get(AType.CATCH_BLOCK);
if (catchAttr != null) {
code.add("\t //" + catchAttr);
code.add("\t " + catchAttr);
}
}
} catch (CodegenException e) {
LOG.debug("Error generate fallback instruction: ", e.getCause());
code.startLine("// error: " + insn);
}
}
@@ -320,7 +231,7 @@ public class MethodGen {
/**
* Return fallback variant of method codegen
*/
private static MethodGen getFallbackMethodGen(MethodNode mth) {
static MethodGen getFallbackMethodGen(MethodNode mth) {
ClassGen clsGen = new ClassGen(mth.getParentClass(), null, true);
return new MethodGen(clsGen, mth);
}
@@ -0,0 +1,218 @@
package jadx.core.codegen;
import jadx.core.Consts;
import jadx.core.deobf.NameMapper;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.instructions.InvokeNode;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.InsnWrapArg;
import jadx.core.dex.instructions.args.NamedArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.args.SSAVar;
import jadx.core.dex.instructions.mods.ConstructorInsn;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.utils.Utils;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
public class NameGen {
private static final Map<String, String> OBJ_ALIAS;
private final Set<String> varNames = new HashSet<String>();
private final boolean fallback;
static {
OBJ_ALIAS = new HashMap<String, String>();
OBJ_ALIAS.put(Consts.CLASS_STRING, "str");
OBJ_ALIAS.put(Consts.CLASS_CLASS, "cls");
OBJ_ALIAS.put(Consts.CLASS_THROWABLE, "th");
OBJ_ALIAS.put(Consts.CLASS_OBJECT, "obj");
OBJ_ALIAS.put("java.util.Iterator", "it");
OBJ_ALIAS.put("java.lang.Boolean", "bool");
OBJ_ALIAS.put("java.lang.Short", "sh");
OBJ_ALIAS.put("java.lang.Integer", "num");
OBJ_ALIAS.put("java.lang.Character", "ch");
OBJ_ALIAS.put("java.lang.Byte", "b");
OBJ_ALIAS.put("java.lang.Float", "f");
OBJ_ALIAS.put("java.lang.Long", "l");
OBJ_ALIAS.put("java.lang.Double", "d");
}
public NameGen(boolean fallback) {
this.fallback = fallback;
}
public String assignArg(RegisterArg arg) {
String name = makeArgName(arg);
if (fallback) {
return name;
}
name = getUniqueVarName(name);
SSAVar sVar = arg.getSVar();
if (sVar != null) {
sVar.setName(name);
}
return name;
}
public String assignNamedArg(NamedArg arg) {
String name = arg.getName();
if (fallback) {
return name;
}
name = getUniqueVarName(name);
arg.setName(name);
return name;
}
public String useArg(RegisterArg arg) {
String name = makeArgName(arg);
varNames.add(name);
return name;
}
private String getUniqueVarName(String name) {
String r = name;
int i = 2;
while (varNames.contains(r)) {
r = name + i;
i++;
}
varNames.add(r);
return r;
}
private String makeArgName(RegisterArg arg) {
String name = arg.getName();
if (fallback) {
String base = "r" + arg.getRegNum();
if (name != null && !name.equals("this")) {
return base + "_" + name;
}
return base;
}
String varName;
if (name != null) {
if ("this".equals(name)) {
return name;
}
varName = name;
} else {
varName = makeNameForType(arg.getType());
}
if (NameMapper.isReserved(varName)) {
return varName + "R";
}
return varName;
}
private static String makeNameForType(ArgType type) {
if (type.isPrimitive()) {
return makeNameForPrimitive(type);
} else if (type.isArray()) {
return makeNameForType(type.getArrayRootElement()) + "Arr";
} else {
return makeNameForObject(type);
}
}
private static String makeNameForPrimitive(ArgType type) {
return type.getPrimitiveType().getShortName().toLowerCase();
}
private static String makeNameForObject(ArgType type) {
if (type.isObject()) {
String alias = getAliasForObject(type.getObject());
if (alias != null) {
return alias;
}
ClassInfo clsInfo = ClassInfo.fromType(type);
String shortName = clsInfo.getShortName();
String vName = fromName(shortName);
if (vName != null) {
return vName;
}
}
return Utils.escape(type.toString());
}
private static String fromName(String name) {
if (name == null || name.isEmpty()) {
return null;
}
if (name.toUpperCase().equals(name)) {
// all characters are upper case
return name.toLowerCase();
}
String v1 = Character.toLowerCase(name.charAt(0)) + name.substring(1);
if (!v1.equals(name)) {
return v1;
}
if (name.length() < 3) {
return name + "Var";
}
return null;
}
public static void guessName(RegisterArg arg) {
SSAVar sVar = arg.getSVar();
if (sVar == null || sVar.getName() != null) {
return;
}
RegisterArg assignArg = sVar.getAssign();
InsnNode assignInsn = assignArg.getParentInsn();
String name = makeNameFromInsn(assignInsn);
if (name != null && !NameMapper.isReserved(name)) {
assignArg.setName(name);
}
}
private static String getAliasForObject(String name) {
return OBJ_ALIAS.get(name);
}
private static String makeNameFromInsn(InsnNode insn) {
switch (insn.getType()) {
case INVOKE:
InvokeNode inv = (InvokeNode) insn;
String name = inv.getCallMth().getName();
if (name.startsWith("get") || name.startsWith("set")) {
return fromName(name.substring(3));
}
if ("iterator".equals(name)) {
return "it";
}
return name;
case CONSTRUCTOR:
ConstructorInsn co = (ConstructorInsn) insn;
return makeNameForObject(co.getClassType().getType());
case ARRAY_LENGTH:
return "length";
case ARITH:
case TERNARY:
case CAST:
for (InsnArg arg : insn.getArguments()) {
if (arg.isInsnWrap()) {
InsnNode wrapInsn = ((InsnWrapArg) arg).getWrapInsn();
String wName = makeNameFromInsn(wrapInsn);
if (wName != null) {
return wName;
}
}
}
break;
default:
break;
}
return null;
}
}
@@ -1,10 +1,9 @@
package jadx.core.codegen;
import jadx.core.dex.attributes.AttributeFlag;
import jadx.core.dex.attributes.AttributeType;
import jadx.core.dex.attributes.DeclareVariablesAttr;
import jadx.core.dex.attributes.ForceReturnAttr;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.DeclareVariablesAttr;
import jadx.core.dex.attributes.nodes.ForceReturnAttr;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.instructions.IndexInsnNode;
import jadx.core.dex.instructions.SwitchNode;
@@ -43,17 +42,19 @@ public class RegionGen extends InsnGen {
if (cont instanceof IBlock) {
makeSimpleBlock((IBlock) cont, code);
} else if (cont instanceof IRegion) {
declareVars(code, cont);
if (cont instanceof Region) {
makeSimpleRegion(code, (Region) cont);
} else if (cont instanceof IfRegion) {
makeIf((IfRegion) cont, code, true);
} else if (cont instanceof SwitchRegion) {
makeSwitch((SwitchRegion) cont, code);
} else if (cont instanceof LoopRegion) {
makeLoop((LoopRegion) cont, code);
} else if (cont instanceof SynchronizedRegion) {
makeSynchronizedRegion((SynchronizedRegion) cont, code);
} else {
declareVars(code, cont);
if (cont instanceof IfRegion) {
makeIf((IfRegion) cont, code, true);
} else if (cont instanceof SwitchRegion) {
makeSwitch((SwitchRegion) cont, code);
} else if (cont instanceof LoopRegion) {
makeLoop((LoopRegion) cont, code);
} else if (cont instanceof SynchronizedRegion) {
makeSynchronizedRegion((SynchronizedRegion) cont, code);
}
}
} else {
throw new CodegenException("Not processed container: " + cont);
@@ -61,8 +62,7 @@ public class RegionGen extends InsnGen {
}
private void declareVars(CodeWriter code, IContainer cont) {
DeclareVariablesAttr declVars =
(DeclareVariablesAttr) cont.getAttributes().get(AttributeType.DECLARE_VARIABLES);
DeclareVariablesAttr declVars = cont.get(AType.DECLARE_VARIABLES);
if (declVars != null) {
for (RegisterArg v : declVars.getVars()) {
code.startLine();
@@ -73,10 +73,11 @@ public class RegionGen extends InsnGen {
}
private void makeSimpleRegion(CodeWriter code, Region region) throws CodegenException {
CatchAttr tc = (CatchAttr) region.getAttributes().get(AttributeType.CATCH_BLOCK);
CatchAttr tc = region.get(AType.CATCH_BLOCK);
if (tc != null) {
makeTryCatch(region, tc.getTryBlock(), code);
} else {
declareVars(code, region);
for (IContainer c : region.getSubBlocks()) {
makeRegion(code, c);
}
@@ -93,9 +94,8 @@ public class RegionGen extends InsnGen {
for (InsnNode insn : block.getInstructions()) {
makeInsn(insn, code);
}
IAttribute attr;
if ((attr = block.getAttributes().get(AttributeType.FORCE_RETURN)) != null) {
ForceReturnAttr retAttr = (ForceReturnAttr) attr;
ForceReturnAttr retAttr = block.get(AType.FORCE_RETURN);
if (retAttr != null) {
makeInsn(retAttr.getReturnInsn(), code);
}
}
@@ -108,6 +108,7 @@ public class RegionGen extends InsnGen {
if (newLine) {
code.startLine();
}
code.attachSourceLine(region.getSourceLine());
code.add("if (");
new ConditionGen(this).add(code, region.getCondition());
code.add(") {");
@@ -130,16 +131,17 @@ public class RegionGen extends InsnGen {
* Connect if-else-if block
*/
private boolean connectElseIf(CodeWriter code, IContainer els) throws CodegenException {
if (els instanceof Region) {
Region re = (Region) els;
List<IContainer> subBlocks = re.getSubBlocks();
if (subBlocks.size() == 1 && subBlocks.get(0) instanceof IfRegion) {
IfRegion ifRegion = (IfRegion) subBlocks.get(0);
if (ifRegion.getAttributes().contains(AttributeFlag.ELSE_IF_CHAIN)) {
makeIf(ifRegion, code, false);
return true;
}
}
if (!els.contains(AFlag.ELSE_IF_CHAIN)) {
return false;
}
if (!(els instanceof Region)) {
return false;
}
List<IContainer> subBlocks = ((Region) els).getSubBlocks();
if (subBlocks.size() == 1
&& subBlocks.get(0) instanceof IfRegion) {
makeIf((IfRegion) subBlocks.get(0), code, false);
return true;
}
return false;
}
@@ -150,7 +152,7 @@ public class RegionGen extends InsnGen {
List<InsnNode> headerInsns = header.getInstructions();
if (headerInsns.size() > 1) {
// write not inlined instructions from header
mth.getAttributes().add(AttributeFlag.INCONSISTENT_CODE);
mth.add(AFlag.INCONSISTENT_CODE);
int last = headerInsns.size() - 1;
for (int i = 0; i < last; i++) {
InsnNode insn = headerInsns.get(i);
@@ -197,7 +199,7 @@ public class RegionGen extends InsnGen {
SwitchNode insn = (SwitchNode) sw.getHeader().getInstructions().get(0);
InsnArg arg = insn.getArg(0);
code.startLine("switch (");
addArg(code, arg);
addArg(code, arg, false);
code.add(") {");
code.incIndent();
@@ -208,7 +210,7 @@ public class RegionGen extends InsnGen {
for (Object k : keys) {
code.startLine("case ");
if (k instanceof IndexInsnNode) {
code.add(staticField((FieldInfo) ((IndexInsnNode) k).getIndex()));
staticField(code, (FieldInfo) ((IndexInsnNode) k).getIndex());
} else {
code.add(TypeGen.literalToString((Integer) k, arg.getType()));
}
@@ -241,7 +243,7 @@ public class RegionGen extends InsnGen {
private void makeTryCatch(IContainer region, TryCatchBlock tryCatchBlock, CodeWriter code)
throws CodegenException {
code.startLine("try {");
region.getAttributes().remove(AttributeType.CATCH_BLOCK);
region.remove(AType.CATCH_BLOCK);
makeRegionIndent(code, region);
ExceptionHandler allHandler = null;
for (ExceptionHandler handler : tryCatchBlock.getHandlers()) {
@@ -257,9 +259,9 @@ public class RegionGen extends InsnGen {
if (allHandler != null) {
makeCatchBlock(code, allHandler);
}
if (tryCatchBlock.getFinalBlock() != null) {
if (tryCatchBlock.getFinalRegion() != null) {
code.startLine("} finally {");
makeRegionIndent(code, tryCatchBlock.getFinalBlock());
makeRegionIndent(code, tryCatchBlock.getFinalRegion());
}
code.startLine('}');
}
@@ -269,9 +271,13 @@ public class RegionGen extends InsnGen {
IContainer region = handler.getHandlerRegion();
if (region != null) {
code.startLine("} catch (");
code.add(handler.isCatchAll() ? "Throwable" : useClass(handler.getCatchType()));
if (handler.isCatchAll()) {
code.add("Throwable");
} else {
useClass(code, handler.getCatchType());
}
code.add(' ');
code.add(mgen.assignNamedArg(handler.getArg()));
code.add(mgen.getNameGen().assignNamedArg(handler.getArg()));
code.add(") {");
makeRegionIndent(code, region);
}
@@ -8,22 +8,8 @@ import jadx.core.utils.exceptions.JadxRuntimeException;
public class TypeGen {
public static String translate(ClassGen clsGen, ArgType type) {
final PrimitiveType stype = type.getPrimitiveType();
if (stype == null) {
return type.toString();
}
if (stype == PrimitiveType.OBJECT) {
return clsGen.useClass(type);
}
if (stype == PrimitiveType.ARRAY) {
return translate(clsGen, type.getArrayElement()) + "[]";
}
return stype.getLongName();
}
public static String signature(ArgType type) {
final PrimitiveType stype = type.getPrimitiveType();
PrimitiveType stype = type.getPrimitiveType();
if (stype == PrimitiveType.OBJECT) {
return Utils.makeQualifiedObjectName(type.getObject());
}
@@ -80,37 +66,91 @@ public class TypeGen {
}
public static String formatShort(short s) {
return "(short) " + wrapNegNum(s < 0, Short.toString(s));
if (s == Short.MAX_VALUE) {
return "Short.MAX_VALUE";
}
if (s == Short.MIN_VALUE) {
return "Short.MIN_VALUE";
}
return "(short) " + Short.toString(s);
}
public static String formatByte(byte b) {
return "(byte) " + wrapNegNum(b < 0, Byte.toString(b));
if (b == Byte.MAX_VALUE) {
return "Byte.MAX_VALUE";
}
if (b == Byte.MIN_VALUE) {
return "Byte.MIN_VALUE";
}
return "(byte) " + Byte.toString(b);
}
public static String formatInteger(int i) {
return wrapNegNum(i < 0, Integer.toString(i));
if (i == Integer.MAX_VALUE) {
return "Integer.MAX_VALUE";
}
if (i == Integer.MIN_VALUE) {
return "Integer.MIN_VALUE";
}
return Integer.toString(i);
}
public static String formatLong(long l) {
if (l == Long.MAX_VALUE) {
return "Long.MAX_VALUE";
}
if (l == Long.MIN_VALUE) {
return "Long.MIN_VALUE";
}
String str = Long.toString(l);
if (Math.abs(l) >= Integer.MAX_VALUE) {
str += "L";
}
return str;
}
public static String formatDouble(double d) {
return wrapNegNum(d < 0, Double.toString(d) + "d");
if (Double.isNaN(d)) {
return "Double.NaN";
}
if (d == Double.NEGATIVE_INFINITY) {
return "Double.NEGATIVE_INFINITY";
}
if (d == Double.POSITIVE_INFINITY) {
return "Double.POSITIVE_INFINITY";
}
if (d == Double.MIN_VALUE) {
return "Double.MIN_VALUE";
}
if (d == Double.MAX_VALUE) {
return "Double.MAX_VALUE";
}
if (d == Double.MIN_NORMAL) {
return "Double.MIN_NORMAL";
}
return Double.toString(d) + "d";
}
public static String formatFloat(float f) {
return wrapNegNum(f < 0, Float.toString(f) + "f");
}
public static String formatLong(long lit) {
String l = Long.toString(lit);
if (lit == Long.MIN_VALUE || Math.abs(lit) >= Integer.MAX_VALUE) {
l += "L";
if (Float.isNaN(f)) {
return "Float.NaN";
}
return wrapNegNum(lit < 0, l);
if (f == Float.NEGATIVE_INFINITY) {
return "Float.NEGATIVE_INFINITY";
}
if (f == Float.POSITIVE_INFINITY) {
return "Float.POSITIVE_INFINITY";
}
if (f == Float.MIN_VALUE) {
return "Float.MIN_VALUE";
}
if (f == Float.MAX_VALUE) {
return "Float.MAX_VALUE";
}
if (f == Float.MIN_NORMAL) {
return "Float.MIN_NORMAL";
}
return Float.toString(f) + "f";
}
private static String wrapNegNum(boolean lz, String str) {
// if (lz)
// return "(" + str + ")";
// else
return str;
}
}
@@ -1,6 +1,6 @@
package jadx.core.dex.attributes;
public enum AttributeFlag {
public enum AFlag {
TRY_ENTER,
TRY_LEAVE,
@@ -0,0 +1,48 @@
package jadx.core.dex.attributes;
import jadx.core.dex.attributes.annotations.AnnotationsList;
import jadx.core.dex.attributes.annotations.MethodParameters;
import jadx.core.dex.attributes.nodes.DeclareVariablesAttr;
import jadx.core.dex.attributes.nodes.EnumClassAttr;
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
import jadx.core.dex.attributes.nodes.ForceReturnAttr;
import jadx.core.dex.attributes.nodes.JadxErrorAttr;
import jadx.core.dex.attributes.nodes.JumpInfo;
import jadx.core.dex.attributes.nodes.LoopInfo;
import jadx.core.dex.attributes.nodes.MethodInlineAttr;
import jadx.core.dex.attributes.nodes.PhiListAttr;
import jadx.core.dex.attributes.nodes.SourceFileAttr;
import jadx.core.dex.nodes.parser.FieldValueAttr;
import jadx.core.dex.trycatch.CatchAttr;
import jadx.core.dex.trycatch.ExcHandlerAttr;
import jadx.core.dex.trycatch.SplitterBlockAttr;
/**
* Attribute types enumeration,
* uses generic type for omit cast after in 'AttributeStorage.get' method
*
* @param <T> attribute class implementation
*/
public class AType<T extends IAttribute> {
private AType() {
}
public static final AType<AttrList<JumpInfo>> JUMP = new AType<AttrList<JumpInfo>>();
public static final AType<AttrList<LoopInfo>> LOOP = new AType<AttrList<LoopInfo>>();
public static final AType<ExcHandlerAttr> EXC_HANDLER = new AType<ExcHandlerAttr>();
public static final AType<CatchAttr> CATCH_BLOCK = new AType<CatchAttr>();
public static final AType<SplitterBlockAttr> SPLITTER_BLOCK = new AType<SplitterBlockAttr>();
public static final AType<ForceReturnAttr> FORCE_RETURN = new AType<ForceReturnAttr>();
public static final AType<FieldValueAttr> FIELD_VALUE = new AType<FieldValueAttr>();
public static final AType<FieldReplaceAttr> FIELD_REPLACE = new AType<FieldReplaceAttr>();
public static final AType<JadxErrorAttr> JADX_ERROR = new AType<JadxErrorAttr>();
public static final AType<MethodInlineAttr> METHOD_INLINE = new AType<MethodInlineAttr>();
public static final AType<EnumClassAttr> ENUM_CLASS = new AType<EnumClassAttr>();
public static final AType<AnnotationsList> ANNOTATION_LIST = new AType<AnnotationsList>();
public static final AType<MethodParameters> ANNOTATION_MTH_PARAMETERS = new AType<MethodParameters>();
public static final AType<PhiListAttr> PHI_LIST = new AType<PhiListAttr>();
public static final AType<SourceFileAttr> SOURCE_FILE = new AType<SourceFileAttr>();
public static final AType<DeclareVariablesAttr> DECLARE_VARIABLES = new AType<DeclareVariablesAttr>();
}
@@ -0,0 +1,30 @@
package jadx.core.dex.attributes;
import jadx.core.utils.Utils;
import java.util.LinkedList;
import java.util.List;
public class AttrList<T> implements IAttribute {
private final AType<AttrList<T>> type;
private final List<T> list = new LinkedList<T>();
public AttrList(AType<AttrList<T>> type) {
this.type = type;
}
public List<T> getList() {
return list;
}
@Override
public AType<AttrList<T>> getType() {
return type;
}
@Override
public String toString() {
return Utils.listToString(list);
}
}
@@ -1,15 +1,96 @@
package jadx.core.dex.attributes;
import jadx.core.dex.attributes.annotations.Annotation;
import java.util.List;
public abstract class AttrNode implements IAttributeNode {
private AttributesList attributesList;
private static final AttributeStorage EMPTY_ATTR_STORAGE = new EmptyAttrStorage();
private AttributeStorage storage = EMPTY_ATTR_STORAGE;
@Override
public AttributesList getAttributes() {
if (attributesList == null) {
attributesList = new AttributesList();
}
return attributesList;
public void add(AFlag flag) {
getStorage().add(flag);
}
@Override
public void addAttr(IAttribute attr) {
getStorage().add(attr);
}
@Override
public <T> void addAttr(AType<AttrList<T>> type, T obj) {
getStorage().add(type, obj);
}
@Override
public void copyAttributesFrom(AttrNode attrNode) {
getStorage().addAll(attrNode.storage);
}
AttributeStorage getStorage() {
AttributeStorage store = storage;
if (store == EMPTY_ATTR_STORAGE) {
store = new AttributeStorage();
storage = store;
}
return store;
}
@Override
public boolean contains(AFlag flag) {
return storage.contains(flag);
}
@Override
public <T extends IAttribute> boolean contains(AType<T> type) {
return storage.contains(type);
}
@Override
public <T extends IAttribute> T get(AType<T> type) {
return storage.get(type);
}
@Override
public Annotation getAnnotation(String cls) {
return storage.getAnnotation(cls);
}
@Override
public <T> List<T> getAll(AType<AttrList<T>> type) {
return storage.getAll(type);
}
@Override
public void remove(AFlag flag) {
storage.remove(flag);
}
@Override
public <T extends IAttribute> void remove(AType<T> type) {
storage.remove(type);
}
@Override
public void removeAttr(IAttribute attr) {
storage.remove(attr);
}
@Override
public void clearAttributes() {
storage.clear();
}
@Override
public List<String> getAttributesStringsList() {
return storage.getAttributeStrings();
}
@Override
public String getAttributesString() {
return storage.toString();
}
}
@@ -0,0 +1,122 @@
package jadx.core.dex.attributes;
import jadx.core.dex.attributes.annotations.Annotation;
import jadx.core.dex.attributes.annotations.AnnotationsList;
import jadx.core.utils.Utils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Storage for different attribute types:
* 1. flags - boolean attribute (set or not)
* 2. attribute - class instance associated with attribute type.
*/
public class AttributeStorage {
private final Set<AFlag> flags;
private final Map<AType<?>, IAttribute> attributes;
public AttributeStorage() {
flags = EnumSet.noneOf(AFlag.class);
attributes = new HashMap<AType<?>, IAttribute>(2);
}
public void add(AFlag flag) {
flags.add(flag);
}
public void add(IAttribute attr) {
attributes.put(attr.getType(), attr);
}
public <T> void add(AType<AttrList<T>> type, T obj) {
AttrList<T> list = get(type);
if (list == null) {
list = new AttrList<T>(type);
add(list);
}
list.getList().add(obj);
}
public void addAll(AttributeStorage otherList) {
flags.addAll(otherList.flags);
attributes.putAll(otherList.attributes);
}
public boolean contains(AFlag flag) {
return flags.contains(flag);
}
public <T extends IAttribute> boolean contains(AType<T> type) {
return attributes.containsKey(type);
}
@SuppressWarnings("unchecked")
public <T extends IAttribute> T get(AType<T> type) {
return (T) attributes.get(type);
}
public Annotation getAnnotation(String cls) {
AnnotationsList aList = get(AType.ANNOTATION_LIST);
return aList == null ? null : aList.get(cls);
}
public <T> List<T> getAll(AType<AttrList<T>> type) {
AttrList<T> attrList = get(type);
if (attrList == null) {
return Collections.emptyList();
}
return attrList.getList();
}
public void remove(AFlag flag) {
flags.remove(flag);
}
public <T extends IAttribute> void remove(AType<T> type) {
attributes.remove(type);
}
public void remove(IAttribute attr) {
AType<?> type = attr.getType();
IAttribute a = attributes.get(type);
if (a == attr) {
attributes.remove(type);
}
}
public void clear() {
flags.clear();
attributes.clear();
}
public List<String> getAttributeStrings() {
int size = flags.size() + attributes.size() + attributes.size();
if (size == 0) {
return Collections.emptyList();
}
List<String> list = new ArrayList<String>(size);
for (AFlag a : flags) {
list.add(a.toString());
}
for (IAttribute a : attributes.values()) {
list.add(a.toString());
}
return list;
}
@Override
public String toString() {
List<String> list = getAttributeStrings();
if (list.isEmpty()) {
return "";
}
return "A:{" + Utils.listToString(list) + "}";
}
}
@@ -1,65 +0,0 @@
package jadx.core.dex.attributes;
public enum AttributeType {
/* Multi attributes */
JUMP(false),
LOOP(false),
CATCH_BLOCK(false),
/* Uniq attributes */
EXC_HANDLER(true),
SPLITTER_BLOCK(true),
FORCE_RETURN(true),
FIELD_VALUE(true),
JADX_ERROR(true),
METHOD_INLINE(true),
FIELD_REPLACE(true),
ENUM_CLASS(true),
ANNOTATION_LIST(true),
ANNOTATION_MTH_PARAMETERS(true),
SOURCE_FILE(true),
// for regions
DECLARE_VARIABLES(true);
private static final int NOT_UNIQ_COUNT;
private final boolean uniq;
private AttributeType(boolean isUniq) {
this.uniq = isUniq;
}
static {
// place all not unique attributes at first
int last = -1;
AttributeType[] vals = AttributeType.values();
for (int i = 0; i < vals.length; i++) {
AttributeType type = vals[i];
if (type.notUniq()) {
last = i;
}
}
NOT_UNIQ_COUNT = last + 1;
}
public static int getNotUniqCount() {
return NOT_UNIQ_COUNT;
}
public boolean isUniq() {
return uniq;
}
public boolean notUniq() {
return !uniq;
}
}
@@ -1,202 +0,0 @@
package jadx.core.dex.attributes;
import jadx.core.dex.attributes.annotations.Annotation;
import jadx.core.dex.attributes.annotations.AnnotationsList;
import jadx.core.utils.Utils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Storage for different attribute types:
* 1. flags - boolean attribute (set or not)
* 2. attribute - class instance associated for attribute type,
* only one attached to node for unique attributes, multiple for others
*/
public final class AttributesList {
private final Set<AttributeFlag> flags;
private final Map<AttributeType, IAttribute> uniqAttr;
private final List<IAttribute> attributes;
private final int[] attrCount;
public AttributesList() {
flags = EnumSet.noneOf(AttributeFlag.class);
uniqAttr = new EnumMap<AttributeType, IAttribute>(AttributeType.class);
attributes = new LinkedList<IAttribute>();
attrCount = new int[AttributeType.getNotUniqCount()];
}
// Flags
public void add(AttributeFlag flag) {
flags.add(flag);
}
public boolean contains(AttributeFlag flag) {
return flags.contains(flag);
}
public void remove(AttributeFlag flag) {
flags.remove(flag);
}
// Attributes
public void add(IAttribute attr) {
if (attr.getType().isUniq()) {
uniqAttr.put(attr.getType(), attr);
} else {
addMultiAttribute(attr);
}
}
private void addMultiAttribute(IAttribute attr) {
attributes.add(attr);
attrCount[attr.getType().ordinal()]++;
}
private int getMultiCountInternal(AttributeType type) {
return attrCount[type.ordinal()];
}
public void addAll(AttributesList otherList) {
flags.addAll(otherList.flags);
uniqAttr.putAll(otherList.uniqAttr);
for (IAttribute attr : otherList.attributes) {
addMultiAttribute(attr);
}
}
public boolean contains(AttributeType type) {
if (type.isUniq()) {
return uniqAttr.containsKey(type);
} else {
return getMultiCountInternal(type) != 0;
}
}
public IAttribute get(AttributeType type) {
if (type.isUniq()) {
return uniqAttr.get(type);
} else {
if (getMultiCountInternal(type) != 0) {
for (IAttribute attr : attributes) {
if (attr.getType() == type) {
return attr;
}
}
}
return null;
}
}
public int getCount(AttributeType type) {
if (type.isUniq()) {
return uniqAttr.containsKey(type) ? 1 : 0;
} else {
return getMultiCountInternal(type);
}
}
public Annotation getAnnotation(String cls) {
AnnotationsList aList = (AnnotationsList) get(AttributeType.ANNOTATION_LIST);
return aList == null ? null : aList.get(cls);
}
public List<IAttribute> getAll(AttributeType type) {
assert type.notUniq();
int count = getMultiCountInternal(type);
if (count == 0) {
return Collections.emptyList();
} else {
List<IAttribute> attrs = new ArrayList<IAttribute>(count);
for (IAttribute attr : attributes) {
if (attr.getType() == type) {
attrs.add(attr);
}
}
return attrs;
}
}
public void remove(AttributeType type) {
if (type.isUniq()) {
uniqAttr.remove(type);
} else {
for (Iterator<IAttribute> it = attributes.iterator(); it.hasNext(); ) {
IAttribute attr = it.next();
if (attr.getType() == type) {
it.remove();
}
}
attrCount[type.ordinal()] = 0;
}
}
public void remove(IAttribute attr) {
AttributeType type = attr.getType();
if (type.isUniq()) {
IAttribute a = uniqAttr.get(type);
if (a == attr) {
uniqAttr.remove(type);
}
} else {
if (getMultiCountInternal(type) == 0) {
return;
}
for (Iterator<IAttribute> it = attributes.iterator(); it.hasNext(); ) {
IAttribute a = it.next();
if (a == attr) {
it.remove();
attrCount[type.ordinal()]--;
}
}
}
}
public void clear() {
flags.clear();
uniqAttr.clear();
attributes.clear();
Arrays.fill(attrCount, 0);
}
public List<String> getAttributeStrings() {
int size = flags.size() + uniqAttr.size() + attributes.size();
if (size == 0) {
return Collections.emptyList();
}
List<String> list = new ArrayList<String>(size);
for (AttributeFlag a : flags) {
list.add(a.toString());
}
for (IAttribute a : uniqAttr.values()) {
list.add(a.toString());
}
for (IAttribute a : attributes) {
list.add(a.toString());
}
return list;
}
@Override
public String toString() {
List<String> list = getAttributeStrings();
if (list.isEmpty()) {
return "";
}
return "A:{" + Utils.listToString(list) + "}";
}
}
@@ -1,56 +0,0 @@
package jadx.core.dex.attributes;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.args.TypedVar;
import jadx.core.dex.nodes.MethodNode;
public final class BlockRegState {
private final RegisterArg[] regs;
public BlockRegState(MethodNode mth) {
this.regs = new RegisterArg[mth.getRegsCount()];
for (int i = 0; i < regs.length; i++) {
regs[i] = new RegisterArg(i);
}
}
public BlockRegState(BlockRegState state) {
this.regs = new RegisterArg[state.regs.length];
System.arraycopy(state.regs, 0, regs, 0, state.regs.length);
}
public void assignReg(RegisterArg arg) {
regs[arg.getRegNum()] = arg;
arg.getTypedVar().getUseList().add(arg);
}
public void use(RegisterArg arg) {
RegisterArg reg = regs[arg.getRegNum()];
TypedVar regType = reg.getTypedVar();
if (regType == null) {
regType = new TypedVar(arg.getType());
reg.forceSetTypedVar(regType);
}
arg.replaceTypedVar(reg);
reg.getTypedVar().getUseList().add(arg);
}
public RegisterArg getRegister(int r) {
return regs[r];
}
@Override
public String toString() {
StringBuilder str = new StringBuilder();
for (RegisterArg reg : regs) {
if (reg.getTypedVar() != null) {
if (str.length() != 0) {
str.append(", ");
}
str.append(reg);
}
}
return str.toString();
}
}
@@ -0,0 +1,55 @@
package jadx.core.dex.attributes;
import jadx.core.dex.attributes.annotations.Annotation;
import java.util.Collections;
import java.util.List;
public class EmptyAttrStorage extends AttributeStorage {
@Override
public boolean contains(AFlag flag) {
return false;
}
@Override
public <T extends IAttribute> boolean contains(AType<T> type) {
return false;
}
@Override
public <T extends IAttribute> T get(AType<T> type) {
return null;
}
@Override
public Annotation getAnnotation(String cls) {
return null;
}
@Override
public <T> List<T> getAll(AType<AttrList<T>> type) {
return Collections.emptyList();
}
@Override
public void clear() {
}
@Override
public void remove(AFlag flag) {
}
@Override
public <T extends IAttribute> void remove(AType<T> type) {
}
@Override
public void remove(IAttribute attr) {
}
@Override
public List<String> getAttributeStrings() {
return Collections.emptyList();
}
}
@@ -2,6 +2,6 @@ package jadx.core.dex.attributes;
public interface IAttribute {
AttributeType getType();
AType<?> getType();
}
@@ -1,7 +1,38 @@
package jadx.core.dex.attributes;
import jadx.core.dex.attributes.annotations.Annotation;
import java.util.List;
public interface IAttributeNode {
AttributesList getAttributes();
void add(AFlag flag);
void addAttr(IAttribute attr);
<T> void addAttr(AType<AttrList<T>> type, T obj);
void copyAttributesFrom(AttrNode attrNode);
boolean contains(AFlag flag);
<T extends IAttribute> boolean contains(AType<T> type);
<T extends IAttribute> T get(AType<T> type);
Annotation getAnnotation(String cls);
<T> List<T> getAll(AType<AttrList<T>> type);
void remove(AFlag flag);
<T extends IAttribute> void remove(AType<T> type);
void removeAttr(IAttribute attr);
void clearAttributes();
List<String> getAttributesStringsList();
String getAttributesString();
}
@@ -6,7 +6,7 @@ import java.util.Map;
public class Annotation {
public static enum Visibility {
public enum Visibility {
BUILD, RUNTIME, SYSTEM
}
@@ -1,6 +1,6 @@
package jadx.core.dex.attributes.annotations;
import jadx.core.dex.attributes.AttributeType;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.utils.Utils;
@@ -33,8 +33,8 @@ public class AnnotationsList implements IAttribute {
}
@Override
public AttributeType getType() {
return AttributeType.ANNOTATION_LIST;
public AType<AnnotationsList> getType() {
return AType.ANNOTATION_LIST;
}
@Override
@@ -1,6 +1,6 @@
package jadx.core.dex.attributes.annotations;
import jadx.core.dex.attributes.AttributeType;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.utils.Utils;
@@ -20,8 +20,8 @@ public class MethodParameters implements IAttribute {
}
@Override
public AttributeType getType() {
return AttributeType.ANNOTATION_MTH_PARAMETERS;
public AType<MethodParameters> getType() {
return AType.ANNOTATION_MTH_PARAMETERS;
}
@Override
@@ -1,5 +1,7 @@
package jadx.core.dex.attributes;
package jadx.core.dex.attributes.nodes;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.utils.Utils;
@@ -22,8 +24,8 @@ public class DeclareVariablesAttr implements IAttribute {
}
@Override
public AttributeType getType() {
return AttributeType.DECLARE_VARIABLES;
public AType<DeclareVariablesAttr> getType() {
return AType.DECLARE_VARIABLES;
}
@Override
@@ -1,5 +1,7 @@
package jadx.core.dex.attributes;
package jadx.core.dex.attributes.nodes;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.MethodNode;
@@ -51,7 +53,7 @@ public class EnumClassAttr implements IAttribute {
private MethodNode staticMethod;
public EnumClassAttr(int fieldsCount) {
this.fields = new ArrayList<EnumClassAttr.EnumField>(fieldsCount);
this.fields = new ArrayList<EnumField>(fieldsCount);
}
public List<EnumField> getFields() {
@@ -67,8 +69,8 @@ public class EnumClassAttr implements IAttribute {
}
@Override
public AttributeType getType() {
return AttributeType.ENUM_CLASS;
public AType<EnumClassAttr> getType() {
return AType.ENUM_CLASS;
}
@Override
@@ -1,5 +1,7 @@
package jadx.core.dex.attributes;
package jadx.core.dex.attributes.nodes;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.info.FieldInfo;
public class FieldReplaceAttr implements IAttribute {
@@ -21,8 +23,8 @@ public class FieldReplaceAttr implements IAttribute {
}
@Override
public AttributeType getType() {
return AttributeType.FIELD_REPLACE;
public AType<FieldReplaceAttr> getType() {
return AType.FIELD_REPLACE;
}
@Override
@@ -1,5 +1,7 @@
package jadx.core.dex.attributes;
package jadx.core.dex.attributes.nodes;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.utils.Utils;
@@ -16,8 +18,8 @@ public class ForceReturnAttr implements IAttribute {
}
@Override
public AttributeType getType() {
return AttributeType.FORCE_RETURN;
public AType<ForceReturnAttr> getType() {
return AType.FORCE_RETURN;
}
@Override
@@ -1,5 +1,7 @@
package jadx.core.dex.attributes;
package jadx.core.dex.attributes.nodes;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.utils.Utils;
public class JadxErrorAttr implements IAttribute {
@@ -15,8 +17,8 @@ public class JadxErrorAttr implements IAttribute {
}
@Override
public AttributeType getType() {
return AttributeType.JADX_ERROR;
public AType<JadxErrorAttr> getType() {
return AType.JADX_ERROR;
}
@Override
@@ -1,22 +1,17 @@
package jadx.core.dex.attributes;
package jadx.core.dex.attributes.nodes;
import jadx.core.utils.InsnUtils;
public class JumpAttribute implements IAttribute {
public class JumpInfo {
private final int src;
private final int dest;
public JumpAttribute(int src, int dest) {
public JumpInfo(int src, int dest) {
this.src = src;
this.dest = dest;
}
@Override
public AttributeType getType() {
return AttributeType.JUMP;
}
public int getSrc() {
return src;
}
@@ -25,11 +20,6 @@ public class JumpAttribute implements IAttribute {
return dest;
}
@Override
public String toString() {
return "JUMP: " + InsnUtils.formatOffset(src) + " -> " + InsnUtils.formatOffset(dest);
}
@Override
public int hashCode() {
return 31 * dest + src;
@@ -46,7 +36,12 @@ public class JumpAttribute implements IAttribute {
if (getClass() != obj.getClass()) {
return false;
}
JumpAttribute other = (JumpAttribute) obj;
JumpInfo other = (JumpInfo) obj;
return dest == other.dest && src == other.src;
}
@Override
public String toString() {
return "JUMP: " + InsnUtils.formatOffset(src) + " -> " + InsnUtils.formatOffset(dest);
}
}
@@ -1,4 +1,6 @@
package jadx.core.dex.attributes;
package jadx.core.dex.attributes.nodes;
import jadx.core.dex.attributes.AttrNode;
public abstract class LineAttrNode extends AttrNode {
@@ -1,5 +1,6 @@
package jadx.core.dex.attributes;
package jadx.core.dex.attributes.nodes;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.Edge;
import jadx.core.utils.BlockUtils;
@@ -10,13 +11,13 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Set;
public class LoopAttr implements IAttribute {
public class LoopInfo {
private final BlockNode start;
private final BlockNode end;
private final Set<BlockNode> loopBlocks;
public LoopAttr(BlockNode start, BlockNode end) {
public LoopInfo(BlockNode start, BlockNode end) {
this.start = start;
this.end = end;
this.loopBlocks = Collections.unmodifiableSet(BlockUtils.getAllPathsBlocks(start, end));
@@ -30,11 +31,6 @@ public class LoopAttr implements IAttribute {
return end;
}
@Override
public AttributeType getType() {
return AttributeType.LOOP;
}
public Set<BlockNode> getLoopBlocks() {
return loopBlocks;
}
@@ -49,7 +45,7 @@ public class LoopAttr implements IAttribute {
for (BlockNode block : blocks) {
// exit: successor node not from this loop, (don't change to getCleanSuccessors)
for (BlockNode s : block.getSuccessors()) {
if (!blocks.contains(s) && !s.getAttributes().contains(AttributeType.EXC_HANDLER)) {
if (!blocks.contains(s) && !s.contains(AType.EXC_HANDLER)) {
nodes.add(block);
}
}
@@ -65,7 +61,7 @@ public class LoopAttr implements IAttribute {
Set<BlockNode> blocks = getLoopBlocks();
for (BlockNode block : blocks) {
for (BlockNode s : block.getSuccessors()) {
if (!blocks.contains(s) && !s.getAttributes().contains(AttributeType.EXC_HANDLER)) {
if (!blocks.contains(s) && !s.contains(AType.EXC_HANDLER)) {
edges.add(new Edge(block, s));
}
}
@@ -1,5 +1,7 @@
package jadx.core.dex.attributes;
package jadx.core.dex.attributes.nodes;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.nodes.InsnNode;
public class MethodInlineAttr implements IAttribute {
@@ -15,8 +17,8 @@ public class MethodInlineAttr implements IAttribute {
}
@Override
public AttributeType getType() {
return AttributeType.METHOD_INLINE;
public AType<MethodInlineAttr> getType() {
return AType.METHOD_INLINE;
}
@Override
@@ -0,0 +1,32 @@
package jadx.core.dex.attributes.nodes;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.instructions.PhiInsn;
import java.util.LinkedList;
import java.util.List;
public class PhiListAttr implements IAttribute {
private final List<PhiInsn> list = new LinkedList<PhiInsn>();
@Override
public AType<PhiListAttr> getType() {
return AType.PHI_LIST;
}
public List<PhiInsn> getList() {
return list;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("PHI: ");
for (PhiInsn phiInsn : list) {
sb.append('r').append(phiInsn.getResult().getRegNum()).append(" ");
}
return sb.toString();
}
}
@@ -1,4 +1,7 @@
package jadx.core.dex.attributes;
package jadx.core.dex.attributes.nodes;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
public class SourceFileAttr implements IAttribute {
@@ -13,8 +16,8 @@ public class SourceFileAttr implements IAttribute {
}
@Override
public AttributeType getType() {
return AttributeType.SOURCE_FILE;
public AType<SourceFileAttr> getType() {
return AType.SOURCE_FILE;
}
@Override
@@ -8,7 +8,7 @@ public class AccessInfo {
private final int accFlags;
public static enum AFType {
public enum AFType {
CLASS, FIELD, METHOD
}
@@ -3,7 +3,7 @@ package jadx.core.dex.info;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.DexNode;
import com.android.dx.io.FieldId;
import com.android.dex.FieldId;
public class FieldInfo {
@@ -7,8 +7,8 @@ import jadx.core.utils.Utils;
import java.util.List;
import com.android.dx.io.MethodId;
import com.android.dx.io.ProtoId;
import com.android.dex.MethodId;
import com.android.dex.ProtoId;
public final class MethodInfo {
@@ -61,6 +61,23 @@ public class ArithNode extends InsnNode {
return op;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof ArithNode) || !super.equals(obj)) {
return false;
}
ArithNode that = (ArithNode) obj;
return op == that.op;
}
@Override
public int hashCode() {
return 31 * super.hashCode() + op.hashCode();
}
@Override
public String toString() {
return InsnUtils.formatOffset(offset) + ": "
@@ -17,7 +17,7 @@ public enum ArithOp {
private final String symbol;
private ArithOp(String symbol) {
ArithOp(String symbol) {
this.symbol = symbol;
}
@@ -3,7 +3,7 @@ package jadx.core.dex.instructions;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.InsnNode;
public class ConstClassNode extends InsnNode {
public final class ConstClassNode extends InsnNode {
private final ArgType clsType;
@@ -16,6 +16,23 @@ public class ConstClassNode extends InsnNode {
return clsType;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof ConstClassNode) || !super.equals(obj)) {
return false;
}
ConstClassNode that = (ConstClassNode) obj;
return clsType.equals(that.clsType);
}
@Override
public int hashCode() {
return 31 * super.hashCode() + clsType.hashCode();
}
@Override
public String toString() {
return super.toString() + " " + clsType;
@@ -2,7 +2,7 @@ package jadx.core.dex.instructions;
import jadx.core.dex.nodes.InsnNode;
public class ConstStringNode extends InsnNode {
public final class ConstStringNode extends InsnNode {
private final String str;
@@ -15,6 +15,23 @@ public class ConstStringNode extends InsnNode {
return str;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof ConstStringNode) || !super.equals(obj)) {
return false;
}
ConstStringNode that = (ConstStringNode) obj;
return str.equals(that.str);
}
@Override
public int hashCode() {
return 31 * super.hashCode() + str.hashCode();
}
@Override
public String toString() {
return super.toString() + " \"" + str + "\"";
@@ -8,7 +8,7 @@ import jadx.core.utils.exceptions.JadxRuntimeException;
import com.android.dx.io.instructions.FillArrayDataPayloadDecodedInstruction;
public class FillArrayNode extends InsnNode {
public final class FillArrayNode extends InsnNode {
private final Object data;
private ArgType elemType;
@@ -53,4 +53,21 @@ public class FillArrayNode extends InsnNode {
elemType = r;
}
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof FillArrayNode) || !super.equals(obj)) {
return false;
}
FillArrayNode that = (FillArrayNode) obj;
return elemType.equals(that.elemType) && data == that.data;
}
@Override
public int hashCode() {
return 31 * super.hashCode() + elemType.hashCode() + data.hashCode();
}
}
@@ -20,6 +20,23 @@ public class GotoNode extends InsnNode {
return target;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof GotoNode) || !super.equals(obj)) {
return false;
}
GotoNode gotoNode = (GotoNode) obj;
return target == gotoNode.target;
}
@Override
public int hashCode() {
return 31 * super.hashCode() + target;
}
@Override
public String toString() {
return super.toString() + "-> " + InsnUtils.formatOffset(target);
@@ -70,6 +70,23 @@ public class IfNode extends GotoNode {
return elseBlock;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof IfNode) || !super.equals(obj)) {
return false;
}
IfNode ifNode = (IfNode) obj;
return op == ifNode.op;
}
@Override
public int hashCode() {
return 31 * super.hashCode() + op.hashCode();
}
@Override
public String toString() {
return InsnUtils.formatOffset(offset) + ": "
@@ -12,7 +12,7 @@ public enum IfOp {
private final String symbol;
private IfOp(String symbol) {
IfOp(String symbol) {
this.symbol = symbol;
}
@@ -23,19 +23,19 @@ public enum IfOp {
public IfOp invert() {
switch (this) {
case EQ:
return IfOp.NE;
return NE;
case NE:
return IfOp.EQ;
return EQ;
case LT:
return IfOp.GE;
return GE;
case LE:
return IfOp.GT;
return GT;
case GT:
return IfOp.LE;
return LE;
case GE:
return IfOp.LT;
return LT;
default:
throw new JadxRuntimeException("Unknown if operations type: " + this);
@@ -16,6 +16,23 @@ public class IndexInsnNode extends InsnNode {
return index;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof IndexInsnNode) || !super.equals(obj)) {
return false;
}
IndexInsnNode that = (IndexInsnNode) obj;
return index == null ? that.index == null : index.equals(that.index);
}
@Override
public int hashCode() {
return 31 * super.hashCode() + (index != null ? index.hashCode() : 0);
}
@Override
public String toString() {
return super.toString() + " " + InsnUtils.indexToString(index);
@@ -12,29 +12,46 @@ 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 java.io.EOFException;
import com.android.dex.Code;
import com.android.dx.io.OpcodeInfo;
import com.android.dx.io.Opcodes;
import com.android.dx.io.instructions.DecodedInstruction;
import com.android.dx.io.instructions.FillArrayDataPayloadDecodedInstruction;
import com.android.dx.io.instructions.PackedSwitchPayloadDecodedInstruction;
import com.android.dx.io.instructions.ShortArrayCodeInput;
import com.android.dx.io.instructions.SparseSwitchPayloadDecodedInstruction;
public class InsnDecoder {
private final MethodNode method;
private final DecodedInstruction[] insnArr;
private final DexNode dex;
private DecodedInstruction[] insnArr;
public InsnDecoder(MethodNode mthNode, Code mthCode) {
public InsnDecoder(MethodNode mthNode) {
this.method = mthNode;
this.dex = method.dex();
this.insnArr = DecodedInstruction.decodeAll(mthCode.getInstructions());
}
public InsnNode[] run() throws DecodeException {
InsnNode[] instructions = new InsnNode[insnArr.length];
public void decodeInsns(Code mthCode) throws DecodeException {
short[] encodedInstructions = mthCode.getInstructions();
int size = encodedInstructions.length;
DecodedInstruction[] decoded = new DecodedInstruction[size];
ShortArrayCodeInput in = new ShortArrayCodeInput(encodedInstructions);
try {
while (in.hasMore()) {
decoded[in.cursor()] = DecodedInstruction.decode(in);
}
} catch (EOFException e) {
throw new DecodeException(method, "", e);
}
insnArr = decoded;
}
public InsnNode[] process() throws DecodeException {
InsnNode[] instructions = new InsnNode[insnArr.length];
for (int i = 0; i < insnArr.length; i++) {
DecodedInstruction rawInsn = insnArr[i];
if (rawInsn != null) {
@@ -48,6 +65,7 @@ public class InsnDecoder {
instructions[i] = null;
}
}
insnArr = null;
return instructions;
}
@@ -58,6 +58,7 @@ public enum InsnType {
TERNARY,
ARGS, // just generate arguments
PHI,
NEW_MULTIDIM_ARRAY // TODO: now multidimensional arrays created using Array.newInstance function
}
@@ -44,6 +44,26 @@ public class InvokeNode extends InsnNode {
return mth;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof InvokeNode) || !super.equals(obj)) {
return false;
}
InvokeNode that = (InvokeNode) obj;
return type == that.type && mth.equals(that.mth);
}
@Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + type.hashCode();
result = 31 * result + mth.hashCode();
return result;
}
@Override
public String toString() {
return InsnUtils.formatOffset(offset) + ": "
@@ -0,0 +1,28 @@
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.RegisterArg;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.utils.Utils;
public class PhiInsn extends InsnNode {
public PhiInsn(int regNum, int predecessors) {
super(InsnType.PHI, predecessors);
setResult(InsnArg.reg(regNum, ArgType.UNKNOWN));
for (int i = 0; i < predecessors; i++) {
addReg(regNum, ArgType.UNKNOWN);
}
}
@Override
public RegisterArg getArg(int n) {
return (RegisterArg) super.getArg(n);
}
@Override
public String toString() {
return "PHI: " + getResult() + " = " + Utils.listToString(getArguments());
}
}
@@ -36,6 +36,29 @@ public class SwitchNode extends InsnNode {
return def;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof SwitchNode) || !super.equals(obj)) {
return false;
}
SwitchNode that = (SwitchNode) obj;
return def == that.def
&& Arrays.equals(keys, that.keys)
&& Arrays.equals(targets, that.targets);
}
@Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + Arrays.hashCode(keys);
result = 31 * result + Arrays.hashCode(targets);
result = 31 * result + def;
return result;
}
@Override
public String toString() {
StringBuilder targ = new StringBuilder();
@@ -48,6 +48,10 @@ public abstract class ArgType {
ArgType.clsp = clsp;
}
public static boolean isClspSet() {
return ArgType.clsp != null;
}
private static ArgType primitive(PrimitiveType stype) {
return new PrimitiveArg(stype);
}
@@ -174,7 +178,7 @@ public abstract class ArgType {
private final int bounds;
public WildcardType(ArgType obj, int bound) {
super(ArgType.OBJECT.getObject());
super(OBJECT.getObject());
this.type = obj;
this.bounds = bound;
}
@@ -214,7 +218,7 @@ public abstract class ArgType {
if (bounds == 0) {
return "?";
}
return "? " + (bounds == -1 ? "super" : "extends") + " " + type.toString();
return "? " + (bounds == -1 ? "super" : "extends") + " " + type;
}
}
@@ -398,7 +402,7 @@ public abstract class ArgType {
}
/**
* @see jadx.core.dex.instructions.args.ArgType.WildcardType#getWildcardBounds()
* @see WildcardType#getWildcardBounds()
*/
public int getWildcardBounds() {
return 0;
@@ -437,7 +441,7 @@ public abstract class ArgType {
}
public static ArgType merge(ArgType a, ArgType b) {
if (b == null || a == null) {
if (a == null || b == null) {
return null;
}
if (a.equals(b)) {
@@ -513,7 +517,7 @@ public abstract class ArgType {
return OBJECT;
} else {
ArgType res = merge(ea, eb);
return res == null ? null : ArgType.array(res);
return res == null ? null : array(res);
}
} else if (b.equals(OBJECT)) {
return OBJECT;
@@ -2,12 +2,16 @@ package jadx.core.dex.instructions.args;
import jadx.core.dex.info.FieldInfo;
// TODO: don't extend RegisterArg (now used as a result of instruction)
public final class FieldArg extends RegisterArg {
private final FieldInfo field;
// instArg equal 'null' for static fields
private final InsnArg instArg;
public FieldArg(FieldInfo field, int regNum) {
super(regNum, field.getType());
public FieldArg(FieldInfo field, InsnArg reg) {
super(-1);
this.instArg = reg;
this.field = field;
}
@@ -15,8 +19,12 @@ public final class FieldArg extends RegisterArg {
return field;
}
public InsnArg getInstanceArg() {
return instArg;
}
public boolean isStatic() {
return regNum == -1;
return instArg == null;
}
@Override
@@ -29,6 +37,37 @@ public final class FieldArg extends RegisterArg {
return false;
}
@Override
public void setType(ArgType type) {
this.type = type;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof FieldArg) || !super.equals(obj)) {
return false;
}
FieldArg fieldArg = (FieldArg) obj;
if (!field.equals(fieldArg.field)) {
return false;
}
if (instArg != null ? !instArg.equals(fieldArg.instArg) : fieldArg.instArg != null) {
return false;
}
return true;
}
@Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + field.hashCode();
result = 31 * result + (instArg != null ? instArg.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "(" + field + ")";
@@ -1,27 +0,0 @@
package jadx.core.dex.instructions.args;
public class ImmutableTypedVar extends TypedVar {
public ImmutableTypedVar(ArgType type) {
super(type);
}
@Override
public boolean isImmutable() {
return true;
}
@Override
public void forceSetType(ArgType newType) {
}
@Override
public boolean merge(TypedVar typedVar) {
return false;
}
@Override
public boolean merge(ArgType type) {
return false;
}
}
@@ -26,10 +26,8 @@ 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 MthParameterArg parameterReg(int regNum, ArgType type) {
return new MthParameterArg(regNum, type);
}
public static LiteralArg lit(long literal, ArgType type) {
@@ -101,11 +99,11 @@ public abstract class InsnArg extends Typed {
break;
case CONST_STR:
arg = wrap(insn);
arg.getTypedVar().forceSetType(ArgType.STRING);
arg.setType(ArgType.STRING);
break;
case CONST_CLASS:
arg = wrap(insn);
arg.getTypedVar().forceSetType(ArgType.CLASS);
arg.setType(ArgType.CLASS);
break;
default:
arg = wrap(insn);
@@ -115,7 +113,7 @@ public abstract class InsnArg extends Typed {
}
public boolean isThis() {
// must be implemented in RegisterArg
// must be implemented in RegisterArg and MthParameterArg
return false;
}
}
@@ -8,7 +8,7 @@ public final class InsnWrapArg extends InsnArg {
public InsnWrapArg(InsnNode insn) {
RegisterArg result = insn.getResult();
this.typedVar = new TypedVar((result != null ? result.getType() : ArgType.VOID));
this.type = result != null ? result.getType() : ArgType.VOID;
this.wrappedInsn = insn;
}
@@ -29,6 +29,6 @@ public final class InsnWrapArg extends InsnArg {
@Override
public String toString() {
return "(wrap: " + typedVar + "\n " + wrappedInsn + ")";
return "(wrap: " + type + "\n " + wrappedInsn + ")";
}
}
@@ -24,7 +24,7 @@ public final class LiteralArg extends InsnArg {
}
}
this.literal = value;
this.typedVar = new TypedVar(type);
this.type = type;
}
public long getLiteral() {
@@ -37,7 +37,7 @@ public final class LiteralArg extends InsnArg {
}
public boolean isInteger() {
PrimitiveType type = typedVar.getType().getPrimitiveType();
PrimitiveType type = this.type.getPrimitiveType();
return (type == PrimitiveType.INT
|| type == PrimitiveType.BYTE
|| type == PrimitiveType.CHAR
@@ -69,10 +69,10 @@ public final class LiteralArg extends InsnArg {
if (getType().equals(ArgType.BOOLEAN) && (value.equals("true") || value.equals("false"))) {
return value;
}
return "(" + value + " " + typedVar + ")";
return "(" + value + " " + type + ")";
} catch (JadxRuntimeException ex) {
// can't convert literal to string
return "(" + literal + " " + typedVar + ")";
return "(" + literal + " " + type + ")";
}
}
}
@@ -0,0 +1,64 @@
package jadx.core.dex.instructions.args;
public class MthParameterArg extends RegisterArg {
private boolean isThis;
public MthParameterArg(int rn, ArgType type) {
super(rn, type);
}
@Override
public boolean isTypeImmutable() {
return true;
}
@Override
public void setType(ArgType type) {
}
public void markAsThis() {
this.isThis = true;
}
@Override
public boolean isThis() {
return isThis;
}
@Override
public String getName() {
if (isThis) {
return "this";
}
return super.getName();
}
@Override
void setSVar(SSAVar sVar) {
if (isThis) {
sVar.setName("this");
}
super.setSVar(sVar);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof MthParameterArg)) {
return false;
}
if (!super.equals(obj)) {
return false;
}
MthParameterArg that = (MthParameterArg) obj;
return isThis == that.isThis;
}
@Override
public int hashCode() {
return 31 * super.hashCode() + (isThis ? 1 : 0);
}
}
@@ -0,0 +1,8 @@
package jadx.core.dex.instructions.args;
public interface Named {
String getName();
void setName(String name);
}
@@ -1,12 +1,12 @@
package jadx.core.dex.instructions.args;
public final class NamedArg extends InsnArg {
public final class NamedArg extends InsnArg implements Named {
private String name;
public NamedArg(String name, ArgType type) {
this.name = name;
this.typedVar = new TypedVar(type);
this.type = type;
}
public String getName() {
@@ -24,6 +24,6 @@ public final class NamedArg extends InsnArg {
@Override
public String toString() {
return "(" + name + " " + typedVar + ")";
return "(" + name + " " + type + ")";
}
}
@@ -16,7 +16,7 @@ public enum PrimitiveType {
private final String shortName;
private final String longName;
private PrimitiveType(String shortName, String longName) {
PrimitiveType(String shortName, String longName) {
this.shortName = shortName;
this.longName = longName;
}
@@ -1,11 +1,12 @@
package jadx.core.dex.instructions.args;
import jadx.core.dex.attributes.AttributeType;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.instructions.ConstClassNode;
import jadx.core.dex.instructions.ConstStringNode;
import jadx.core.dex.instructions.IndexInsnNode;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.PhiInsn;
import jadx.core.dex.nodes.DexNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.InsnNode;
@@ -14,17 +15,18 @@ import jadx.core.dex.nodes.parser.FieldValueAttr;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class RegisterArg extends InsnArg {
public class RegisterArg extends InsnArg implements Named {
private static final Logger LOG = LoggerFactory.getLogger(RegisterArg.class);
protected final int regNum;
protected SSAVar sVar;
public RegisterArg(int rn) {
this.regNum = rn;
}
public RegisterArg(int rn, ArgType type) {
this.typedVar = new TypedVar(type);
this.type = type;
this.regNum = rn;
}
@@ -37,18 +39,49 @@ public class RegisterArg extends InsnArg {
return true;
}
public InsnNode getAssignInsn() {
for (InsnArg arg : getTypedVar().getUseList()) {
InsnNode assignInsn = arg.getParentInsn();
if (assignInsn == null) {
// assign as function argument
return null;
} else if (assignInsn.getResult() != null
&& assignInsn.getResult().getRegNum() == regNum) {
return assignInsn;
}
public SSAVar getSVar() {
return sVar;
}
void setSVar(SSAVar sVar) {
this.sVar = sVar;
}
public String getName() {
if (sVar == null) {
return null;
}
return null;
return sVar.getName();
}
public void setName(String name) {
if (sVar != null) {
sVar.setName(name);
}
}
public boolean isNameEquals(InsnArg arg) {
String n = getName();
if (n == null || !(arg instanceof Named)) {
return false;
}
return n.equals(((Named) arg).getName());
}
@Override
public void setType(ArgType type) {
if (sVar != null) {
sVar.setType(type);
}
}
public void mergeDebugInfo(ArgType type, String name) {
setType(type);
setName(name);
}
public void forceType(ArgType type) {
this.type = type;
}
/**
@@ -58,54 +91,82 @@ public class RegisterArg extends InsnArg {
*/
public Object getConstValue(DexNode dex) {
InsnNode parInsn = getAssignInsn();
if (parInsn != null) {
InsnType insnType = parInsn.getType();
switch (insnType) {
case CONST:
return parInsn.getArg(0);
case CONST_STR:
return ((ConstStringNode) parInsn).getString();
case CONST_CLASS:
return ((ConstClassNode) parInsn).getClsType();
case SGET:
FieldInfo f = (FieldInfo) ((IndexInsnNode) parInsn).getIndex();
FieldNode fieldNode = dex.resolveField(f);
if (fieldNode != null) {
FieldValueAttr attr = (FieldValueAttr) fieldNode.getAttributes().get(AttributeType.FIELD_VALUE);
if (attr != null) {
return attr.getValue();
}
} else {
LOG.warn("Field {} not found in dex {}", f, dex);
if (parInsn == null) {
return null;
}
InsnType insnType = parInsn.getType();
switch (insnType) {
case CONST:
return parInsn.getArg(0);
case CONST_STR:
return ((ConstStringNode) parInsn).getString();
case CONST_CLASS:
return ((ConstClassNode) parInsn).getClsType();
case SGET:
FieldInfo f = (FieldInfo) ((IndexInsnNode) parInsn).getIndex();
FieldNode fieldNode = dex.resolveField(f);
if (fieldNode != null) {
FieldValueAttr attr = fieldNode.get(AType.FIELD_VALUE);
if (attr != null) {
return attr.getValue();
}
break;
}
} else {
LOG.warn("Field {} not found in dex {}", f, dex);
}
break;
}
return null;
}
@Override
public boolean isThis() {
if (isRegister()) {
String name = getTypedVar().getName();
if ("this".equals(name)) {
return true;
}
// maybe it was moved from 'this' register
InsnNode ai = getAssignInsn();
if (ai != null && ai.getType() == InsnType.MOVE) {
InsnArg arg = ai.getArg(0);
if (arg != this && arg.isThis()) {
return true;
}
if ("this".equals(getName())) {
return true;
}
// maybe it was moved from 'this' register
InsnNode ai = getAssignInsn();
if (ai != null && ai.getType() == InsnType.MOVE) {
InsnArg arg = ai.getArg(0);
if (arg != this) {
return arg.isThis();
}
}
return false;
}
public InsnNode getAssignInsn() {
if (sVar == null) {
return null;
}
RegisterArg assign = sVar.getAssign();
if (assign != null) {
return assign.getParentInsn();
}
return null;
}
public InsnNode getPhiAssignInsn() {
PhiInsn usePhi = sVar.getUsedInPhi();
if (usePhi != null) {
return usePhi;
}
RegisterArg assign = sVar.getAssign();
if (assign != null) {
InsnNode parent = assign.getParentInsn();
if (parent != null && parent.getType() == InsnType.PHI) {
return parent;
}
}
return null;
}
public boolean equalRegisterAndType(RegisterArg arg) {
return regNum == arg.regNum && type.equals(arg.type);
}
@Override
public int hashCode() {
return regNum * 31 + typedVar.hashCode();
return (regNum * 31 + type.hashCode()) * 31 + (sVar != null ? sVar.hashCode() : 0);
}
@Override
@@ -116,15 +177,36 @@ public class RegisterArg extends InsnArg {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
if (!(obj instanceof RegisterArg)) {
return false;
}
RegisterArg other = (RegisterArg) obj;
return regNum == other.regNum && typedVar.equals(other.typedVar);
if (regNum != other.regNum) {
return false;
}
if (!type.equals(other.type)) {
return false;
}
if (sVar != null && !sVar.equals(other.getSVar())) {
return false;
}
return true;
}
@Override
public String toString() {
return "(r" + regNum + " " + typedVar + ")";
StringBuilder sb = new StringBuilder();
sb.append("(r");
sb.append(regNum);
if (sVar != null) {
sb.append("_").append(sVar.getVersion());
}
if (getName() != null) {
sb.append(" '").append(getName()).append("'");
}
sb.append(" ");
sb.append(type);
sb.append(")");
return sb.toString();
}
}
@@ -0,0 +1,149 @@
package jadx.core.dex.instructions.args;
import jadx.core.dex.instructions.PhiInsn;
import java.util.ArrayList;
import java.util.List;
public class SSAVar {
private final int regNum;
private final int version;
private VarName varName;
private RegisterArg assign;
private final List<RegisterArg> useList = new ArrayList<RegisterArg>(2);
private PhiInsn usedInPhi;
private ArgType type;
public SSAVar(int regNum, int v, RegisterArg assign) {
this.regNum = regNum;
this.version = v;
this.assign = assign;
if (assign != null) {
assign.setSVar(this);
}
}
public int getRegNum() {
return regNum;
}
public int getVersion() {
return version;
}
public RegisterArg getAssign() {
return assign;
}
public void setAssign(RegisterArg assign) {
this.assign = assign;
}
public List<RegisterArg> getUseList() {
return useList;
}
public int getUseCount() {
return useList.size();
}
public void use(RegisterArg arg) {
if (arg.getSVar() != null) {
arg.getSVar().removeUse(arg);
}
arg.setSVar(this);
useList.add(arg);
}
public void removeUse(RegisterArg arg) {
for (int i = 0, useListSize = useList.size(); i < useListSize; i++) {
if (useList.get(i) == arg) {
useList.remove(i);
break;
}
}
}
public void setUsedInPhi(PhiInsn usedInPhi) {
this.usedInPhi = usedInPhi;
}
public PhiInsn getUsedInPhi() {
return usedInPhi;
}
public boolean isUsedInPhi() {
return usedInPhi != null;
}
public int getVariableUseCount() {
if (!isUsedInPhi()) {
return useList.size();
}
return useList.size() + usedInPhi.getResult().getSVar().getUseCount();
}
public ArgType getType() {
return type;
}
public void setType(ArgType type) {
this.type = type;
if (assign != null) {
assign.type = type;
}
for (int i = 0, useListSize = useList.size(); i < useListSize; i++) {
useList.get(i).type = type;
}
}
public void setName(String name) {
if (name != null) {
if (varName == null) {
varName = new VarName();
}
varName.setName(name);
}
}
public String getName() {
if (varName == null) {
return null;
}
return varName.getName();
}
public VarName getVarName() {
return varName;
}
public void setVarName(VarName varName) {
this.varName = varName;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof SSAVar)) {
return false;
}
SSAVar ssaVar = (SSAVar) o;
return regNum == ssaVar.regNum && version == ssaVar.version;
}
@Override
public int hashCode() {
return 31 * regNum + version;
}
@Override
public String toString() {
return "r" + regNum + "_" + version;
}
}
@@ -1,71 +1,31 @@
package jadx.core.dex.instructions.args;
import java.util.List;
public abstract class Typed {
TypedVar typedVar;
public TypedVar getTypedVar() {
return typedVar;
}
protected ArgType type;
public ArgType getType() {
return typedVar.getType();
return type;
}
public boolean merge(Typed var) {
return typedVar.merge(var.getTypedVar());
public void setType(ArgType type) {
this.type = type;
}
public boolean merge(ArgType var) {
return typedVar.merge(var);
public boolean isTypeImmutable() {
return false;
}
public void forceSetTypedVar(TypedVar arg) {
this.typedVar = arg;
}
public void mergeDebugInfo(Typed arg) {
merge(arg);
mergeName(arg);
}
protected void mergeName(Typed arg) {
getTypedVar().mergeName(arg.getTypedVar());
}
public boolean replaceTypedVar(Typed var) {
TypedVar curVar = this.typedVar;
TypedVar newVar = var.typedVar;
if (curVar == newVar) {
return false;
public boolean merge(ArgType newType) {
ArgType m = ArgType.merge(type, newType);
if (m != null && !m.equals(type)) {
setType(m);
return true;
}
if (curVar != null) {
if (curVar.isImmutable()) {
moveInternals(newVar, curVar);
} else {
newVar.merge(curVar);
moveInternals(curVar, newVar);
this.typedVar = newVar;
}
} else {
this.typedVar = newVar;
}
return true;
return false;
}
private void moveInternals(TypedVar from, TypedVar to) {
List<InsnArg> curUseList = from.getUseList();
if (curUseList.size() != 0) {
for (InsnArg arg : curUseList) {
if (arg != this) {
arg.forceSetTypedVar(to);
}
}
to.getUseList().addAll(curUseList);
curUseList.clear();
}
to.mergeName(from);
public boolean merge(InsnArg arg) {
return merge(arg.getType());
}
}
@@ -1,116 +0,0 @@
package jadx.core.dex.instructions.args;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class TypedVar {
private ArgType type;
private final List<InsnArg> useList = new ArrayList<InsnArg>(2);
private String name;
public TypedVar(ArgType initType) {
this.type = initType;
}
public ArgType getType() {
return type;
}
/**
* This method must be used very carefully
*/
public void forceSetType(ArgType newType) {
type = newType;
}
public boolean merge(TypedVar typedVar) {
return merge(typedVar.getType());
}
public boolean merge(ArgType mtype) {
ArgType res = ArgType.merge(type, mtype);
if (res != null && !type.equals(res)) {
this.type = res;
return true;
} else {
return false;
}
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<InsnArg> getUseList() {
return useList;
}
public void removeUse(InsnArg arg) {
Iterator<InsnArg> it = useList.iterator();
while (it.hasNext()) {
InsnArg use = it.next();
if (use == arg) {
it.remove();
}
}
}
public void mergeName(TypedVar arg) {
String argName = arg.getName();
if (argName != null) {
setName(argName);
} else if (getName() != null) {
arg.setName(getName());
}
}
public boolean isImmutable() {
return false;
}
@Override
public int hashCode() {
return type.hashCode() * 31 + (name == null ? 0 : name.hashCode());
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof TypedVar)) {
return false;
}
TypedVar other = (TypedVar) obj;
if (!type.equals(other.type)) {
return false;
}
if (name == null) {
if (other.name != null) {
return false;
}
} else if (!name.equals(other.name)) {
return false;
}
return true;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
if (name != null) {
sb.append('\'').append(name).append("' ");
}
sb.append(type);
return sb.toString();
}
}
@@ -0,0 +1,18 @@
package jadx.core.dex.instructions.args;
public class VarName {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return name;
}
}
@@ -14,7 +14,7 @@ public class ConstructorInsn extends InsnNode {
private final CallType callType;
private final RegisterArg instanceArg;
private static enum CallType {
private enum CallType {
CONSTRUCTOR, // just new instance
SUPER, // super call
THIS, // call constructor from other constructor
@@ -26,7 +26,6 @@ public class ConstructorInsn extends InsnNode {
this.callMth = invoke.getCallMth();
ClassInfo classType = callMth.getDeclClass();
instanceArg = (RegisterArg) invoke.getArg(0);
instanceArg.setParentInsn(this);
if (instanceArg.isThis()) {
if (classType.equals(mth.getParentClass().getClassInfo())) {
@@ -42,11 +41,15 @@ public class ConstructorInsn extends InsnNode {
} else {
callType = CallType.CONSTRUCTOR;
setResult(instanceArg);
// convert from 'use' to 'assign'
instanceArg.getSVar().setAssign(instanceArg);
}
instanceArg.getSVar().removeUse(instanceArg);
for (int i = 1; i < invoke.getArgsCount(); i++) {
addArg(invoke.getArg(i));
}
offset = invoke.getOffset();
setSourceLine(invoke.getSourceLine());
}
public MethodInfo getCallMth() {
@@ -73,6 +76,29 @@ public class ConstructorInsn extends InsnNode {
return callType == CallType.SELF;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof ConstructorInsn) || !super.equals(o)) {
return false;
}
ConstructorInsn that = (ConstructorInsn) o;
return callMth.equals(that.callMth)
&& callType == that.callType
&& instanceArg.equals(that.instanceArg);
}
@Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + callMth.hashCode();
result = 31 * result + callType.hashCode();
result = 31 * result + instanceArg.hashCode();
return result;
}
@Override
public String toString() {
return super.toString() + " " + callMth + " " + callType;
@@ -33,6 +33,23 @@ public class TernaryInsn extends InsnNode {
return condition;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof TernaryInsn) || !super.equals(obj)) {
return false;
}
TernaryInsn that = (TernaryInsn) obj;
return condition.equals(that.condition);
}
@Override
public int hashCode() {
return 31 * super.hashCode() + condition.hashCode();
}
@Override
public String toString() {
return InsnUtils.formatOffset(offset) + ": TERNARY"
@@ -1,15 +1,15 @@
package jadx.core.dex.nodes;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.AttrNode;
import jadx.core.dex.attributes.AttributeFlag;
import jadx.core.dex.attributes.AttributeType;
import jadx.core.dex.attributes.BlockRegState;
import jadx.core.dex.attributes.LoopAttr;
import jadx.core.dex.attributes.nodes.LoopInfo;
import jadx.core.utils.InsnUtils;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
public class BlockNode extends AttrNode implements IBlock {
@@ -29,10 +29,7 @@ public class BlockNode extends AttrNode implements IBlock {
// immediate dominator
private BlockNode idom;
// blocks on which dominates this block
private final List<BlockNode> dominatesOn = new ArrayList<BlockNode>(1);
private BlockRegState startState;
private BlockRegState endState;
private List<BlockNode> dominatesOn = Collections.emptyList();
public BlockNode(int id, int offset) {
this.id = id;
@@ -64,9 +61,17 @@ public class BlockNode extends AttrNode implements IBlock {
}
public void lock() {
cleanSuccessors = Collections.unmodifiableList(cleanSuccessors);
successors = Collections.unmodifiableList(successors);
predecessors = Collections.unmodifiableList(predecessors);
cleanSuccessors = lockList(cleanSuccessors);
successors = lockList(successors);
predecessors = lockList(predecessors);
dominatesOn = lockList(dominatesOn);
}
List<BlockNode> lockList(List<BlockNode> list) {
if (list.isEmpty()) {
return Collections.emptyList();
}
return Collections.unmodifiableList(list);
}
/**
@@ -74,26 +79,32 @@ public class BlockNode extends AttrNode implements IBlock {
*/
private static List<BlockNode> cleanSuccessors(BlockNode block) {
List<BlockNode> sucList = block.getSuccessors();
List<BlockNode> nodes = new ArrayList<BlockNode>(sucList.size());
LoopAttr loop = (LoopAttr) block.getAttributes().get(AttributeType.LOOP);
if (loop == null) {
for (BlockNode b : sucList) {
if (!b.getAttributes().contains(AttributeType.EXC_HANDLER)) {
nodes.add(b);
}
}
} else {
for (BlockNode b : sucList) {
if (!b.getAttributes().contains(AttributeType.EXC_HANDLER)) {
// don't follow back edge
if (loop.getStart() == b && loop.getEnd() == block) {
continue;
}
nodes.add(b);
if (sucList.isEmpty()) {
return sucList;
}
List<BlockNode> toRemove = new LinkedList<BlockNode>();
for (BlockNode b : sucList) {
if (b.contains(AType.EXC_HANDLER)) {
toRemove.add(b);
} else if (b.contains(AFlag.SYNTHETIC)) {
List<BlockNode> s = b.getSuccessors();
if (s.size() == 1 && s.get(0).contains(AType.EXC_HANDLER)) {
toRemove.add(b);
}
}
}
return nodes.size() == sucList.size() ? sucList : nodes;
if (block.contains(AFlag.LOOP_END)) {
List<LoopInfo> loops = block.getAll(AType.LOOP);
for (LoopInfo loop : loops) {
toRemove.add(loop.getStart());
}
}
if (toRemove.isEmpty()) {
return sucList;
}
List<BlockNode> result = new ArrayList<BlockNode>(sucList);
result.removeAll(toRemove);
return result;
}
@Override
@@ -146,28 +157,19 @@ public class BlockNode extends AttrNode implements IBlock {
return dominatesOn;
}
public BlockRegState getStartState() {
return startState;
}
public void setStartState(BlockRegState startState) {
this.startState = startState;
}
public BlockRegState getEndState() {
return endState;
}
public void setEndState(BlockRegState endState) {
this.endState = endState;
public void addDominatesOn(BlockNode block) {
if (dominatesOn.isEmpty()) {
dominatesOn = new LinkedList<BlockNode>();
}
dominatesOn.add(block);
}
public boolean isSynthetic() {
return getAttributes().contains(AttributeFlag.SYNTHETIC);
return contains(AFlag.SYNTHETIC);
}
public boolean isReturnBlock() {
return getAttributes().contains(AttributeFlag.RETURN);
return contains(AFlag.RETURN);
}
@Override
@@ -199,6 +201,11 @@ public class BlockNode extends AttrNode implements IBlock {
return true;
}
@Override
public String baseString() {
return Integer.toString(id);
}
@Override
public String toString() {
return "B:" + id + ":" + InsnUtils.formatOffset(startOffset);
@@ -2,10 +2,11 @@ package jadx.core.dex.nodes;
import jadx.core.Consts;
import jadx.core.codegen.CodeWriter;
import jadx.core.dex.attributes.AttributeType;
import jadx.core.dex.attributes.LineAttrNode;
import jadx.core.dex.attributes.SourceFileAttr;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.annotations.Annotation;
import jadx.core.dex.attributes.nodes.JadxErrorAttr;
import jadx.core.dex.attributes.nodes.LineAttrNode;
import jadx.core.dex.attributes.nodes.SourceFileAttr;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.info.AccessInfo.AFType;
import jadx.core.dex.info.ClassInfo;
@@ -30,10 +31,10 @@ import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.android.dx.io.ClassData;
import com.android.dx.io.ClassData.Field;
import com.android.dx.io.ClassData.Method;
import com.android.dx.io.ClassDef;
import com.android.dex.ClassData;
import com.android.dex.ClassData.Field;
import com.android.dex.ClassData.Method;
import com.android.dex.ClassDef;
public class ClassNode extends LineAttrNode implements ILoadable {
private static final Logger LOG = LoggerFactory.getLogger(ClassNode.class);
@@ -103,15 +104,16 @@ public class ClassNode extends LineAttrNode implements ILoadable {
int sfIdx = cls.getSourceFileIndex();
if (sfIdx != DexNode.NO_INDEX) {
String fileName = dex.getString(sfIdx);
if (!this.getFullName().contains(fileName.replace(".java", ""))) {
this.getAttributes().add(new SourceFileAttr(fileName));
if (!this.getFullName().contains(fileName.replace(".java", ""))
&& !fileName.equals("SourceFile")) {
this.addAttr(new SourceFileAttr(fileName));
LOG.debug("Class '{}' compiled from '{}'", this, fileName);
}
}
// restore original access flags from dalvik annotation if present
int accFlagsValue;
Annotation a = getAttributes().getAnnotation(Consts.DALVIK_INNER_CLASS);
Annotation a = getAnnotation(Consts.DALVIK_INNER_CLASS);
if (a != null) {
accFlagsValue = (Integer) a.getValues().get("accessFlags");
} else {
@@ -138,8 +140,7 @@ public class ClassNode extends LineAttrNode implements ILoadable {
private void loadStaticValues(ClassDef cls, List<FieldNode> staticFields) throws DecodeException {
for (FieldNode f : staticFields) {
if (f.getAccessFlags().isFinal()) {
FieldValueAttr nullValue = new FieldValueAttr(null);
f.getAttributes().add(nullValue);
f.addAttr(new FieldValueAttr(null));
}
}
@@ -151,7 +152,7 @@ public class ClassNode extends LineAttrNode implements ILoadable {
for (FieldNode f : staticFields) {
AccessInfo accFlags = f.getAccessFlags();
if (accFlags.isStatic() && accFlags.isFinal()) {
FieldValueAttr fv = (FieldValueAttr) f.getAttributes().get(AttributeType.FIELD_VALUE);
FieldValueAttr fv = f.get(AType.FIELD_VALUE);
if (fv != null && fv.getValue() != null) {
if (accFlags.isPublic()) {
dex.getConstFields().put(fv.getValue(), f);
@@ -204,9 +205,14 @@ public class ClassNode extends LineAttrNode implements ILoadable {
}
@Override
public void load() throws DecodeException {
public void load() {
for (MethodNode mth : getMethods()) {
mth.load();
try {
mth.load();
} catch (DecodeException e) {
LOG.error("Method load error", e);
mth.addAttr(new JadxErrorAttr(e));
}
}
for (ClassNode innerCls : getInnerClasses()) {
innerCls.load();
@@ -13,23 +13,23 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.android.dx.io.ClassData;
import com.android.dx.io.ClassData.Method;
import com.android.dx.io.ClassDef;
import com.android.dx.io.Code;
import com.android.dx.io.DexBuffer;
import com.android.dx.io.DexBuffer.Section;
import com.android.dx.io.FieldId;
import com.android.dx.io.MethodId;
import com.android.dx.io.ProtoId;
import com.android.dx.merge.TypeList;
import com.android.dex.ClassData;
import com.android.dex.ClassData.Method;
import com.android.dex.ClassDef;
import com.android.dex.Code;
import com.android.dex.Dex;
import com.android.dex.Dex.Section;
import com.android.dex.FieldId;
import com.android.dex.MethodId;
import com.android.dex.ProtoId;
import com.android.dex.TypeList;
public class DexNode {
public static final int NO_INDEX = -1;
private final RootNode root;
private final DexBuffer dexBuf;
private final Dex dexBuf;
private final List<ClassNode> classes = new ArrayList<ClassNode>();
private final String[] strings;
@@ -1,12 +1,12 @@
package jadx.core.dex.nodes;
import jadx.core.dex.attributes.LineAttrNode;
import jadx.core.dex.attributes.nodes.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;
import com.android.dex.ClassData.Field;
public class FieldNode extends LineAttrNode {
@@ -3,4 +3,7 @@ package jadx.core.dex.nodes;
import jadx.core.dex.attributes.IAttributeNode;
public interface IContainer extends IAttributeNode {
// unique id for use in 'toString()' method
String baseString();
}
@@ -17,4 +17,13 @@ public class InsnContainer extends AttrNode implements IBlock {
return insns;
}
@Override
public String baseString() {
return Integer.toString(insns.size());
}
@Override
public String toString() {
return "InsnContainer:" + insns.size();
}
}

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