Compare commits

...

31 Commits

Author SHA1 Message Date
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
247 changed files with 6862 additions and 2817 deletions
+9 -1
View File
@@ -3,10 +3,18 @@ jdk:
- oraclejdk7
- openjdk7
- openjdk6
env:
- TERM=dumb
before_install:
- chmod +x gradlew
script:
- TERM=dumb ./gradlew clean build dist
- ./gradlew clean build dist
after_success:
- ./gradlew jacocoTestReport coveralls
notifications:
email:
- skylot@gmail.com
+11 -9
View File
@@ -1,24 +1,26 @@
## 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
### 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`
@@ -51,4 +53,4 @@ Example:
*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.4.0'
}
}
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 = '1.12'
}
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=http\://services.gradle.org/distributions/gradle-1.12-bin.zip
+2 -2
View File
@@ -5,8 +5,8 @@ applicationName = 'jadx'
dependencies {
compile(project(':jadx-core'))
compile 'com.beust:jcommander:1.30'
compile 'ch.qos.logback:logback-classic:1.1.1'
compile 'com.beust:jcommander:1.35'
compile 'ch.qos.logback:logback-classic:1.1.2'
}
startScripts {
+19 -14
View File
@@ -1,6 +1,6 @@
package jadx.cli;
import jadx.api.Decompiler;
import jadx.api.JadxDecompiler;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.exceptions.JadxException;
@@ -14,37 +14,41 @@ public class JadxCLI {
public static void main(String[] args) {
try {
JadxCLIArgs jadxArgs = new JadxCLIArgs(args);
checkArgs(jadxArgs);
processAndSave(jadxArgs);
} catch (Exception e) {
JadxCLIArgs jadxArgs = new JadxCLIArgs();
if (processArgs(jadxArgs, args)) {
processAndSave(jadxArgs);
}
} catch (JadxException e) {
LOG.error(e.getMessage());
System.exit(1);
}
}
private static void processAndSave(JadxCLIArgs jadxArgs) {
static void processAndSave(JadxCLIArgs jadxArgs) throws JadxException {
try {
Decompiler jadx = new Decompiler(jadxArgs);
JadxDecompiler jadx = new JadxDecompiler(jadxArgs);
jadx.loadFiles(jadxArgs.getInput());
jadx.setOutputDir(jadxArgs.getOutDir());
jadx.save();
LOG.info("done");
} catch (Throwable e) {
LOG.error("jadx error:", e);
throw new JadxException("jadx error: " + e.getMessage(), e);
}
int errorsCount = ErrorsCounter.getErrorCount();
if (errorsCount != 0) {
if (ErrorsCounter.getErrorCount() != 0) {
ErrorsCounter.printReport();
throw new JadxException("finished with errors");
}
System.exit(errorsCount);
LOG.info("done");
}
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 +68,6 @@ public class JadxCLI {
if (outputDir.exists() && !outputDir.isDirectory()) {
throw new JadxException("Output directory exists as file " + outputDir);
}
return true;
}
}
@@ -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() {
+6 -2
View File
@@ -1,8 +1,12 @@
ext.jadxClasspath = 'clsp-data/android-4.3.jar'
dependencies {
compile 'com.google.android.tools:dx:1.7'
compile files('lib/dx-1.8.jar')
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) {
@@ -9,6 +9,7 @@ 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 +23,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 +31,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 +44,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,12 +56,12 @@ public final class Decompiler {
private List<IDexTreeVisitor> passes;
private List<JavaClass> classes;
public Decompiler() {
public JadxDecompiler() {
this.args = new DefaultJadxArgs();
init();
}
public Decompiler(IJadxArgs jadxArgs) {
public JadxDecompiler(IJadxArgs jadxArgs) {
this.args = jadxArgs;
init();
}
@@ -72,23 +72,34 @@ public final class Decompiler {
}
void init() {
reset();
if (outDir == null) {
outDir = new File("jadx-output");
}
this.passes = Jadx.getPassesList(args, outDir);
}
public void loadFile(File file) throws IOException, DecodeException {
void reset() {
ClassInfo.clearCache();
ErrorsCounter.reset();
classes = null;
}
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 +110,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 +136,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 +151,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 +174,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());
}
});
}
@@ -177,14 +192,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 +211,9 @@ public final class Decompiler {
}
return null;
}
@Override
public String toString() {
return "jadx decompiler";
}
}
+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);
+27 -17
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;
@@ -18,12 +19,14 @@ 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 +54,26 @@ public class Jadx {
passes.add(new FallbackModeVisitor());
} else {
passes.add(new BlockMakerVisitor());
passes.add(new SSATransform());
passes.add(new DebugInfoVisitor());
passes.add(new TypeInference());
passes.add(new TypeResolver());
passes.add(new ConstInlinerVisitor());
passes.add(new FinishTypeInference());
if (args.isRawCFGOutput()) {
passes.add(new DotGraphVisitor(outDir, false, true));
}
passes.add(new ConstInlinerVisitor());
passes.add(new FinishTypeResolver());
passes.add(new EliminatePhiNodes());
passes.add(new ModVisitor());
passes.add(new EnumVisitor());
if (args.isCFGOutput()) {
passes.add(new DotGraphVisitor(outDir, false));
}
passes.add(new CodeShrinker());
passes.add(new RegionMakerVisitor());
passes.add(new IfRegionVisitor());
passes.add(new ReturnVisitor());
passes.add(new CodeShrinker());
passes.add(new SimplifyVisitor());
@@ -84,6 +87,10 @@ public class Jadx {
passes.add(new MethodInlineVisitor());
passes.add(new ClassModifier());
passes.add(new PrepareForCodeGen());
if (args.isCFGOutput()) {
passes.add(new DotGraphVisitor(outDir, false));
}
}
passes.add(new CodeGen(args));
return passes;
@@ -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();
}
}
@@ -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;
@@ -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);
@@ -130,7 +128,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 +141,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 +165,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(" & ");
}
@@ -206,7 +214,7 @@ public class ClassGen {
private void addMethods(CodeWriter code) {
for (MethodNode mth : cls.getMethods()) {
if (!mth.getAttributes().contains(AttributeFlag.DONT_GENERATE)) {
if (!mth.contains(AFlag.DONT_GENERATE)) {
try {
if (code.getLine() != clsDeclLine) {
code.newLine();
@@ -233,11 +241,11 @@ 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");
}
if (mthGen.addDefinition(code)) {
code.add(' ');
@@ -254,15 +262,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) {
@@ -277,81 +285,92 @@ 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(", ");
}
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) {
new ClassGen(f.getCls(), this, fallback).addClassBody(code);
}
if (it.hasNext()) {
code.add(',');
}
code.add(')');
}
if (enumFields.getFields().isEmpty()) {
code.startLine();
if (f.getCls() != null) {
new ClassGen(f.getCls(), this, fallback).addClassBody(code);
}
if (it.hasNext()) {
code.add(',');
}
code.add(';');
code.newLine();
}
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) {
final 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 +460,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() {
@@ -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;
@@ -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,7 +492,8 @@ 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));
@@ -539,10 +558,12 @@ 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()) {
@@ -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,14 +1,11 @@
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;
@@ -22,10 +19,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 +32,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 +60,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 +70,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 +88,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 +96,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 +117,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 +131,17 @@ public class MethodGen {
ArgType type = arg.getType();
if (type.isArray()) {
ArgType elType = type.getArrayElement();
argsCode.add(TypeGen.translate(classGen, elType));
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,80 +150,13 @@ 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)) {
if (mth.contains(AType.JADX_ERROR)) {
code.startLine("throw new UnsupportedOperationException(\"Method not decompiled: ");
code.add(mth.toString());
code.add("\");");
JadxErrorAttr err = (JadxErrorAttr) mth.getAttributes().get(AttributeType.JADX_ERROR);
JadxErrorAttr err = mth.get(AType.JADX_ERROR);
code.startLine("/* JADX: method processing error */");
Throwable cause = err.getCause();
if (cause != null) {
@@ -241,7 +166,7 @@ public class MethodGen {
code.add("*/");
}
makeMethodDump(code);
} else if (mth.getAttributes().contains(AttributeFlag.INCONSISTENT_CODE)) {
} else if (mth.contains(AFlag.INCONSISTENT_CODE)) {
code.startLine("/*");
addFallbackMethodCode(code);
code.startLine("*/");
@@ -281,24 +206,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(getFallbackMethodGen(mth).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 +233,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);
}
}
@@ -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) {
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);
}
}
public 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);
@@ -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,20 +8,6 @@ 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();
if (stype == PrimitiveType.OBJECT) {
@@ -1,6 +1,6 @@
package jadx.core.dex.attributes;
public enum AttributeFlag {
public enum AFlag {
TRY_ENTER,
TRY_LEAVE,
@@ -0,0 +1,45 @@
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> {
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();
}
@@ -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;
@@ -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
@@ -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) + ": "
@@ -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) + ": "
@@ -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();
@@ -437,7 +437,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)) {
@@ -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 = false;
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 + ")";
}
}
@@ -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;
}
}
@@ -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,27 @@ 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);
}
}
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 +152,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 +196,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();
}
}
@@ -1,6 +1,6 @@
package jadx.core.dex.nodes;
import jadx.core.dex.attributes.LineAttrNode;
import jadx.core.dex.attributes.nodes.LineAttrNode;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
@@ -81,12 +81,16 @@ public class InsnNode extends LineAttrNode {
arguments.set(n, arg);
}
/**
* Replace instruction arg with another using recursive search.
* <br>
* <b>Caution:</b> this method don't change usage information for replaced argument.
*/
public boolean replaceArg(InsnArg from, InsnArg to) {
int count = getArgsCount();
for (int i = 0; i < count; i++) {
InsnArg arg = arguments.get(i);
if (arg == from) {
// TODO correct remove from use list
setArg(i, to);
return true;
}
@@ -1,9 +1,10 @@
package jadx.core.dex.nodes;
import jadx.core.dex.attributes.AttributeFlag;
import jadx.core.dex.attributes.JumpAttribute;
import jadx.core.dex.attributes.LineAttrNode;
import jadx.core.dex.attributes.LoopAttr;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.JumpInfo;
import jadx.core.dex.attributes.nodes.LineAttrNode;
import jadx.core.dex.attributes.nodes.LoopInfo;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.info.AccessInfo.AFType;
import jadx.core.dex.info.ClassInfo;
@@ -14,8 +15,9 @@ import jadx.core.dex.instructions.InsnDecoder;
import jadx.core.dex.instructions.SwitchNode;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.MthParameterArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.parser.DebugInfoParser;
import jadx.core.dex.instructions.args.SSAVar;
import jadx.core.dex.nodes.parser.SignatureParser;
import jadx.core.dex.regions.Region;
import jadx.core.dex.trycatch.ExcHandlerAttr;
@@ -35,10 +37,10 @@ import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.android.dx.io.ClassData.Method;
import com.android.dx.io.Code;
import com.android.dx.io.Code.CatchHandler;
import com.android.dx.io.Code.Try;
import com.android.dex.ClassData.Method;
import com.android.dex.Code;
import com.android.dex.Code.CatchHandler;
import com.android.dex.Code.Try;
public class MethodNode extends LineAttrNode implements ILoadable {
private static final Logger LOG = LoggerFactory.getLogger(MethodNode.class);
@@ -49,12 +51,14 @@ public class MethodNode extends LineAttrNode implements ILoadable {
private final Method methodData;
private int regsCount;
private List<InsnNode> instructions;
private InsnNode[] instructions;
private int debugInfoOffset;
private boolean noCode;
private ArgType retType;
private RegisterArg thisArg;
private List<RegisterArg> argsList;
private List<SSAVar> sVars = Collections.emptyList();
private Map<ArgType, List<ArgType>> genericMap;
private List<BlockNode> blocks;
@@ -63,7 +67,7 @@ public class MethodNode extends LineAttrNode implements ILoadable {
private Region region;
private List<ExceptionHandler> exceptionHandlers = Collections.emptyList();
private List<LoopAttr> loops = Collections.emptyList();
private List<LoopInfo> loops = Collections.emptyList();
public MethodNode(ClassNode classNode, Method mthData) {
this.mthInfo = MethodInfo.fromDex(classNode.dex(), mthData.getMethodIndex());
@@ -87,32 +91,21 @@ public class MethodNode extends LineAttrNode implements ILoadable {
regsCount = mthCode.getRegistersSize();
initMethodTypes();
InsnDecoder decoder = new InsnDecoder(this, mthCode);
InsnNode[] insnByOffset = decoder.run();
instructions = new ArrayList<InsnNode>();
for (InsnNode insn : insnByOffset) {
if (insn != null) {
instructions.add(insn);
}
}
((ArrayList<InsnNode>) instructions).trimToSize();
InsnDecoder decoder = new InsnDecoder(this);
decoder.decodeInsns(mthCode);
instructions = decoder.process();
initTryCatches(mthCode, insnByOffset);
initJumps(insnByOffset);
initTryCatches(mthCode);
initJumps();
int debugInfoOffset = mthCode.getDebugInfoOffset();
if (debugInfoOffset > 0) {
DebugInfoParser debugInfoParser = new DebugInfoParser(this, debugInfoOffset, insnByOffset);
debugInfoParser.process();
if (instructions.size() != 0) {
int line = instructions.get(0).getSourceLine();
if (line != 0) {
this.setSourceLine(line - 1);
}
}
}
this.debugInfoOffset = mthCode.getDebugInfoOffset();
} catch (Exception e) {
if (!noCode) {
noCode = true;
// load without code
load();
noCode = false;
}
throw new DecodeException(this, "Load method exception", e);
}
}
@@ -129,13 +122,10 @@ public class MethodNode extends LineAttrNode implements ILoadable {
if (noCode) {
return;
}
if (instructions != null) {
instructions.clear();
}
instructions = null;
blocks = null;
exitBlocks = null;
exceptionHandlers.clear();
noCode = true;
}
private boolean parseSignature() {
@@ -190,8 +180,9 @@ public class MethodNode extends LineAttrNode implements ILoadable {
if (accFlags.isStatic()) {
thisArg = null;
} else {
thisArg = InsnArg.immutableReg(pos - 1, parentClass.getClassInfo().getType());
thisArg.getTypedVar().setName("this");
MthParameterArg arg = InsnArg.parameterReg(pos - 1, parentClass.getClassInfo().getType());
arg.markAsThis();
thisArg = arg;
}
if (args.isEmpty()) {
argsList = Collections.emptyList();
@@ -199,7 +190,7 @@ public class MethodNode extends LineAttrNode implements ILoadable {
}
argsList = new ArrayList<RegisterArg>(args.size());
for (ArgType arg : args) {
argsList.add(InsnArg.immutableReg(pos, arg));
argsList.add(InsnArg.parameterReg(pos, arg));
pos += arg.getRegCount();
}
}
@@ -216,7 +207,7 @@ public class MethodNode extends LineAttrNode implements ILoadable {
}
public RegisterArg removeFirstArgument() {
this.getAttributes().add(AttributeFlag.SKIP_FIRST_ARG);
this.add(AFlag.SKIP_FIRST_ARG);
return argsList.remove(0);
}
@@ -232,24 +223,11 @@ public class MethodNode extends LineAttrNode implements ILoadable {
return genericMap;
}
// TODO: move to external class
private void initTryCatches(Code mthCode, InsnNode[] insnByOffset) {
private void initTryCatches(Code mthCode) {
InsnNode[] insnByOffset = instructions;
CatchHandler[] catchBlocks = mthCode.getCatchHandlers();
Try[] tries = mthCode.getTries();
// Bug in dx library already fixed (Try.getHandlerOffset() replaced by Try.getCatchHandlerIndex())
// and we don't need this mapping anymore,
// but in maven repository still old version
Set<Integer> handlerSet = new HashSet<Integer>(tries.length);
for (Try aTry : tries) {
handlerSet.add(aTry.getHandlerOffset());
}
List<Integer> handlerList = new ArrayList<Integer>(catchBlocks.length);
handlerList.addAll(handlerSet);
Collections.sort(handlerList);
handlerSet = null;
// -------------------
int hc = 0;
Set<Integer> addrs = new HashSet<Integer>();
List<TryCatchBlock> catches = new ArrayList<TryCatchBlock>(catchBlocks.length);
@@ -278,7 +256,7 @@ public class MethodNode extends LineAttrNode implements ILoadable {
// each handler must be only in one try/catch block
for (TryCatchBlock ct1 : catches) {
for (TryCatchBlock ct2 : catches) {
if (ct1 != ct2 && ct2.getHandlers().containsAll(ct1.getHandlers())) {
if (ct1 != ct2 && ct2.containsAllHandlers(ct1)) {
for (ExceptionHandler h : ct1.getHandlers()) {
ct2.removeHandler(this, h);
}
@@ -294,31 +272,35 @@ public class MethodNode extends LineAttrNode implements ILoadable {
int addr = eh.getHandleOffset();
// assert addrs.add(addr) : "Instruction already contains EXC_HANDLER attribute";
ExcHandlerAttr ehAttr = new ExcHandlerAttr(ct, eh);
insnByOffset[addr].getAttributes().add(ehAttr);
insnByOffset[addr].addAttr(ehAttr);
}
}
// attach TRY_ENTER, TRY_LEAVE attributes to instructions
for (Try aTry : tries) {
int catchNum = handlerList.indexOf(aTry.getHandlerOffset());
TryCatchBlock block = catches.get(catchNum);
int catchNum = aTry.getCatchHandlerIndex();
TryCatchBlock catchBlock = catches.get(catchNum);
int offset = aTry.getStartAddress();
int end = offset + aTry.getInstructionCount() - 1;
insnByOffset[offset].getAttributes().add(AttributeFlag.TRY_ENTER);
insnByOffset[offset].add(AFlag.TRY_ENTER);
while (offset <= end && offset >= 0) {
block.addInsn(insnByOffset[offset]);
catchBlock.addInsn(insnByOffset[offset]);
offset = InsnDecoder.getNextInsnOffset(insnByOffset, offset);
}
if (insnByOffset[end] != null) {
insnByOffset[end].getAttributes().add(AttributeFlag.TRY_LEAVE);
insnByOffset[end].add(AFlag.TRY_LEAVE);
}
}
}
private void initJumps(InsnNode[] insnByOffset) {
for (InsnNode insn : getInstructions()) {
int offset = insn.getOffset();
private void initJumps() {
InsnNode[] insnByOffset = instructions;
for (int offset = 0; offset < insnByOffset.length; offset++) {
InsnNode insn = insnByOffset[offset];
if (insn == null) {
continue;
}
switch (insn.getType()) {
case SWITCH: {
SwitchNode sw = (SwitchNode) insn;
@@ -352,7 +334,7 @@ public class MethodNode extends LineAttrNode implements ILoadable {
}
private static void addJump(InsnNode[] insnByOffset, int offset, int target) {
insnByOffset[target].getAttributes().add(new JumpAttribute(offset, target));
insnByOffset[target].addAttr(AType.JUMP, new JumpInfo(offset, target));
}
public String getName() {
@@ -372,20 +354,20 @@ public class MethodNode extends LineAttrNode implements ILoadable {
return noCode;
}
public List<InsnNode> getInstructions() {
public InsnNode[] getInstructions() {
return instructions;
}
public void unloadInsnArr() {
this.instructions = null;
}
public void initBasicBlocks() {
blocks = new ArrayList<BlockNode>();
exitBlocks = new ArrayList<BlockNode>(1);
}
public void finishBasicBlocks() {
// after filling basic blocks we don't need instructions list anymore
instructions.clear();
instructions = null;
((ArrayList<BlockNode>) blocks).trimToSize();
((ArrayList<BlockNode>) exitBlocks).trimToSize();
@@ -417,15 +399,15 @@ public class MethodNode extends LineAttrNode implements ILoadable {
this.exitBlocks.add(exitBlock);
}
public void registerLoop(LoopAttr loop) {
public void registerLoop(LoopInfo loop) {
if (loops.isEmpty()) {
loops = new ArrayList<LoopAttr>(5);
loops = new ArrayList<LoopInfo>(5);
}
loops.add(loop);
}
public LoopAttr getLoopForBlock(BlockNode block) {
for (LoopAttr loop : loops) {
public LoopInfo getLoopForBlock(BlockNode block) {
for (LoopInfo loop : loops) {
if (loop.getLoopBlocks().contains(block)) {
return loop;
}
@@ -451,8 +433,12 @@ public class MethodNode extends LineAttrNode implements ILoadable {
return handler;
}
public List<ExceptionHandler> getExceptionHandlers() {
return Collections.unmodifiableList(exceptionHandlers);
public Iterable<ExceptionHandler> getExceptionHandlers() {
return exceptionHandlers;
}
public boolean isNoExceptionHandlers() {
return exceptionHandlers.isEmpty();
}
/**
@@ -480,6 +466,28 @@ public class MethodNode extends LineAttrNode implements ILoadable {
return regsCount;
}
public int getDebugInfoOffset() {
return debugInfoOffset;
}
public SSAVar makeNewSVar(int regNum, int[] versions, RegisterArg arg) {
SSAVar var = new SSAVar(regNum, versions[regNum], arg);
versions[regNum]++;
if (sVars.isEmpty()) {
sVars = new ArrayList<SSAVar>();
}
sVars.add(var);
return var;
}
public void removeSVar(SSAVar var) {
sVars.remove(var);
}
public List<SSAVar> getSVars() {
return sVars;
}
public AccessInfo getAccessFlags() {
return accFlags;
}
@@ -16,7 +16,7 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import com.android.dx.io.DexBuffer.Section;
import com.android.dex.Dex.Section;
public class AnnotationsParser {
@@ -44,17 +44,17 @@ public class AnnotationsParser {
int annotatedParametersCount = section.readInt();
if (classAnnotationsOffset != 0) {
cls.getAttributes().add(readAnnotationSet(classAnnotationsOffset));
cls.addAttr(readAnnotationSet(classAnnotationsOffset));
}
for (int i = 0; i < fieldsCount; i++) {
FieldNode f = cls.searchFieldById(section.readInt());
f.getAttributes().add(readAnnotationSet(section.readInt()));
f.addAttr(readAnnotationSet(section.readInt()));
}
for (int i = 0; i < annotatedMethodsCount; i++) {
MethodNode m = cls.searchMethodById(section.readInt());
m.getAttributes().add(readAnnotationSet(section.readInt()));
m.addAttr(readAnnotationSet(section.readInt()));
}
for (int i = 0; i < annotatedParametersCount; i++) {
@@ -66,7 +66,7 @@ public class AnnotationsParser {
for (int j = 0; j < size; j++) {
params.getParamList().add(readAnnotationSet(ss.readInt()));
}
mth.getAttributes().add(params);
mth.addAttr(params);
}
}
@@ -1,6 +1,6 @@
package jadx.core.dex.nodes.parser;
import jadx.core.dex.attributes.SourceFileAttr;
import jadx.core.dex.attributes.nodes.SourceFileAttr;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.DexNode;
@@ -10,7 +10,7 @@ import jadx.core.utils.exceptions.DecodeException;
import java.util.List;
import com.android.dx.io.DexBuffer.Section;
import com.android.dex.Dex.Section;
public class DebugInfoParser {
@@ -63,7 +63,7 @@ public class DebugInfoParser {
int id = section.readUleb128() - 1;
if (id != DexNode.NO_INDEX) {
String name = dex.getString(id);
mthArgs.get(i).getTypedVar().setName(name);
mthArgs.get(i).setName(name);
}
}
@@ -75,6 +75,7 @@ public class DebugInfoParser {
// process '0' instruction
addrChange(-1, 1, line);
setLine(addr, line);
int c = section.readByte() & 0xFF;
while (c != DBG_END_SEQUENCE) {
@@ -82,6 +83,7 @@ public class DebugInfoParser {
case DBG_ADVANCE_PC: {
int addrInc = section.readUleb128();
addr = addrChange(addr, addrInc, line);
setLine(addr, line);
break;
}
case DBG_ADVANCE_LINE: {
@@ -135,7 +137,7 @@ public class DebugInfoParser {
int idx = section.readUleb128() - 1;
if (idx != DexNode.NO_INDEX) {
String sourceFile = dex.getString(idx);
mth.getAttributes().add(new SourceFileAttr(sourceFile));
mth.addAttr(new SourceFileAttr(sourceFile));
}
break;
}
@@ -143,9 +145,10 @@ public class DebugInfoParser {
default: {
if (c >= DBG_FIRST_SPECIAL) {
int adjustedOpcode = c - DBG_FIRST_SPECIAL;
line += DBG_LINE_BASE + (adjustedOpcode % DBG_LINE_RANGE);
int addrInc = adjustedOpcode / DBG_LINE_RANGE;
addr = addrChange(addr, addrInc, line);
line += DBG_LINE_BASE + (adjustedOpcode % DBG_LINE_RANGE);
setLine(addr, line);
} else {
throw new DecodeException("Unknown debug insn code: " + c);
}
@@ -161,6 +164,7 @@ public class DebugInfoParser {
setVar(var);
}
}
setSourceLines(addr, insnByOffset.length, line);
}
private int addrChange(int addr, int addrInc, int line) {
@@ -170,7 +174,6 @@ public class DebugInfoParser {
if (insn == null) {
continue;
}
insn.setSourceLine(line);
for (InsnArg arg : insn.getArguments()) {
if (arg.isRegister()) {
activeRegisters[((RegisterArg) arg).getRegNum()] = arg;
@@ -181,9 +184,23 @@ public class DebugInfoParser {
activeRegisters[res.getRegNum()] = res;
}
}
setSourceLines(addr, newAddr, line);
return newAddr;
}
private void setSourceLines(int start, int end, int line) {
for (int offset = start + 1; offset < end; offset++) {
setLine(offset, line);
}
}
private void setLine(int offset, int line) {
InsnNode insn = insnByOffset[offset];
if (insn != null) {
insn.setSourceLine(line);
}
}
private void startVar(LocalVar var, int addr, int line) {
int regNum = var.getRegNum();
LocalVar prev = locals[regNum];
@@ -209,19 +226,18 @@ public class DebugInfoParser {
}
private static void fillLocals(InsnNode insn, LocalVar var) {
if (insn.getResult() != null) {
merge(insn.getResult(), var);
}
merge(insn.getResult(), var);
for (InsnArg arg : insn.getArguments()) {
merge(arg, var);
}
}
private static void merge(InsnArg arg, LocalVar var) {
if (arg != null
&& arg.isRegister()
&& var.getRegNum() == ((RegisterArg) arg).getRegNum()) {
arg.mergeDebugInfo(var);
if (arg != null && arg.isRegister()) {
RegisterArg reg = (RegisterArg) arg;
if (var.getRegNum() == reg.getRegNum()) {
reg.mergeDebugInfo(var.getType(), var.getName());
}
}
}
}
@@ -8,16 +8,34 @@ import jadx.core.utils.exceptions.DecodeException;
import java.util.ArrayList;
import java.util.List;
import com.android.dx.io.DexBuffer.Section;
import com.android.dx.io.EncodedValueReader;
import com.android.dx.util.Leb128Utils;
import com.android.dex.Dex.Section;
import com.android.dex.Leb128;
public class EncValueParser extends EncodedValueReader {
public class EncValueParser {
public static final int ENCODED_BYTE = 0x00;
public static final int ENCODED_SHORT = 0x02;
public static final int ENCODED_CHAR = 0x03;
public static final int ENCODED_INT = 0x04;
public static final int ENCODED_LONG = 0x06;
public static final int ENCODED_FLOAT = 0x10;
public static final int ENCODED_DOUBLE = 0x11;
public static final int ENCODED_STRING = 0x17;
public static final int ENCODED_TYPE = 0x18;
public static final int ENCODED_FIELD = 0x19;
public static final int ENCODED_ENUM = 0x1b;
public static final int ENCODED_METHOD = 0x1a;
public static final int ENCODED_ARRAY = 0x1c;
public static final int ENCODED_ANNOTATION = 0x1d;
public static final int ENCODED_NULL = 0x1e;
public static final int ENCODED_BOOLEAN = 0x1f;
protected final Section in;
private final DexNode dex;
public EncValueParser(DexNode dex, Section in) {
super(in);
this.in = in;
this.dex = dex;
}
@@ -64,7 +82,7 @@ public class EncValueParser extends EncodedValueReader {
return FieldInfo.fromDex(dex, parseUnsignedInt(size));
case ENCODED_ARRAY:
int count = Leb128Utils.readUnsignedLeb128(in);
int count = Leb128.readUnsignedLeb128(in);
List<Object> values = new ArrayList<Object>(count);
for (int i = 0; i < count; i++) {
values.add(parseValue());
@@ -72,7 +90,7 @@ public class EncValueParser extends EncodedValueReader {
return values;
case ENCODED_ANNOTATION:
return AnnotationsParser.readAnnotation(dex, (Section) in, false);
return AnnotationsParser.readAnnotation(dex, in, false);
}
throw new DecodeException("Unknown encoded value type: 0x" + Integer.toHexString(type));
}
@@ -1,6 +1,6 @@
package jadx.core.dex.nodes.parser;
import jadx.core.dex.attributes.AttributeType;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
public class FieldValueAttr implements IAttribute {
@@ -12,8 +12,8 @@ public class FieldValueAttr implements IAttribute {
}
@Override
public AttributeType getType() {
return AttributeType.FIELD_VALUE;
public AType<FieldValueAttr> getType() {
return AType.FIELD_VALUE;
}
public Object getValue() {
@@ -2,24 +2,25 @@ package jadx.core.dex.nodes.parser;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.args.TypedVar;
import jadx.core.dex.nodes.DexNode;
import jadx.core.utils.InsnUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
final class LocalVar extends RegisterArg {
final class LocalVar {
private static final Logger LOG = LoggerFactory.getLogger(LocalVar.class);
private boolean isEnd;
private int regNum;
private String name;
private ArgType type;
private boolean isEnd;
private int startAddr;
private int endAddr;
public LocalVar(DexNode dex, int rn, int nameId, int typeId, int signId) {
super(rn);
this.regNum = rn;
String name = (nameId == DexNode.NO_INDEX ? null : dex.getString(nameId));
ArgType type = (typeId == DexNode.NO_INDEX ? null : dex.getType(typeId));
String sign = (signId == DexNode.NO_INDEX ? null : dex.getString(signId));
@@ -28,8 +29,8 @@ final class LocalVar extends RegisterArg {
}
public LocalVar(RegisterArg arg) {
super(arg.getRegNum());
init(arg.getTypedVar().getName(), arg.getType(), null);
this.regNum = arg.getRegNum();
init(arg.getName(), arg.getType(), null);
}
private void init(String name, ArgType type, String sign) {
@@ -43,9 +44,8 @@ final class LocalVar extends RegisterArg {
LOG.error("Can't parse signature for local variable: " + sign, e);
}
}
TypedVar tv = new TypedVar(type);
tv.setName(name);
forceSetTypedVar(tv);
this.name = name;
this.type = type;
}
private boolean checkSignature(ArgType type, String sign, ArgType gType) {
@@ -59,7 +59,6 @@ final class LocalVar extends RegisterArg {
} else if (el.isGenericType()) {
apply = true;
} else {
LOG.debug("Local var signature from debug info not generic: {}, parsed: {}", sign, gType);
apply = false;
}
return apply;
@@ -75,6 +74,18 @@ final class LocalVar extends RegisterArg {
this.endAddr = addr;
}
public int getRegNum() {
return regNum;
}
public String getName() {
return name;
}
public ArgType getType() {
return type;
}
public boolean isEnd() {
return isEnd;
}
@@ -87,6 +98,16 @@ final class LocalVar extends RegisterArg {
return endAddr;
}
@Override
public boolean equals(Object obj) {
return super.equals(obj);
}
@Override
public int hashCode() {
return super.hashCode();
}
@Override
public String toString() {
return super.toString() + " " + (isEnd
@@ -6,14 +6,18 @@ import jadx.core.dex.attributes.annotations.Annotation;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.utils.exceptions.JadxRuntimeException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SignatureParser {
private static final Logger LOG = LoggerFactory.getLogger(SignatureParser.class);
private static final char STOP_CHAR = 0;
private final String sign;
@@ -30,7 +34,7 @@ public class SignatureParser {
@SuppressWarnings("unchecked")
public static SignatureParser fromNode(IAttributeNode node) {
Annotation a = node.getAttributes().getAnnotation(Consts.DALVIK_SIGNATURE);
Annotation a = node.getAnnotation(Consts.DALVIK_SIGNATURE);
if (a == null) {
return null;
}
@@ -154,7 +158,13 @@ public class SignatureParser {
} while (ch != '<' && ch != ';');
if (ch == ';') {
return ArgType.object(incompleteType ? slice() : inclusiveSlice());
String obj;
if (incompleteType) {
obj = slice().replace('/', '.');
} else {
obj = inclusiveSlice();
}
return ArgType.object(obj);
} else {
// generic type start ('<')
String obj = slice();
@@ -179,7 +189,7 @@ public class SignatureParser {
}
private ArgType[] consumeGenericArgs() {
List<ArgType> list = new ArrayList<ArgType>(1);
List<ArgType> list = new LinkedList<ArgType>();
ArgType type;
do {
if (lookAhead('*')) {
@@ -208,7 +218,7 @@ public class SignatureParser {
*/
public Map<ArgType, List<ArgType>> consumeGenericMap() {
if (!lookAhead('<')) {
return null;
return Collections.emptyMap();
}
Map<ArgType, List<ArgType>> map = new LinkedHashMap<ArgType, List<ArgType>>(2);
consume('<');
@@ -217,6 +227,10 @@ public class SignatureParser {
break;
}
String id = consumeUntil(':');
if (id == null) {
LOG.error("Can't parse generic map: {}", sign);
return Collections.emptyMap();
}
tryConsume(':');
List<ArgType> types = consumeExtendsTypesList();
map.put(ArgType.genericType(id), types);
@@ -6,8 +6,8 @@ import jadx.core.utils.exceptions.DecodeException;
import java.util.List;
import com.android.dx.io.DexBuffer.Section;
import com.android.dx.util.Leb128Utils;
import com.android.dex.Dex.Section;
import com.android.dex.Leb128;
public class StaticValuesParser extends EncValueParser {
@@ -16,10 +16,10 @@ public class StaticValuesParser extends EncValueParser {
}
public int processFields(List<FieldNode> fields) throws DecodeException {
int count = Leb128Utils.readUnsignedLeb128(in);
int count = Leb128.readUnsignedLeb128(in);
for (int i = 0; i < count; i++) {
Object value = parseValue();
fields.get(i).getAttributes().add(new FieldValueAttr(value));
fields.get(i).addAttr(new FieldValueAttr(value));
}
return count;
}
@@ -24,6 +24,10 @@ public final class Compare {
return insn.getArg(1);
}
public IfNode getInsn() {
return insn;
}
public Compare invert() {
insn.invertCondition();
return this;
@@ -2,10 +2,13 @@ package jadx.core.dex.regions;
import jadx.core.dex.instructions.IfNode;
import jadx.core.dex.instructions.IfOp;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.InsnWrapArg;
import jadx.core.dex.instructions.args.LiteralArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.utils.exceptions.JadxRuntimeException;
import java.util.ArrayList;
@@ -132,6 +135,7 @@ public final class IfCondition {
public static IfCondition simplify(IfCondition cond) {
if (cond.isCompare()) {
Compare c = cond.getCompare();
simplifyCmpOp(c);
if (c.getOp() == IfOp.EQ && c.getB().isLiteral() && c.getB().equals(LiteralArg.FALSE)) {
return not(new IfCondition(c.invert()));
} else {
@@ -177,6 +181,22 @@ public final class IfCondition {
return cond;
}
private static void simplifyCmpOp(Compare c) {
if (!c.getA().isInsnWrap()) {
return;
}
if (!c.getB().isLiteral() || ((LiteralArg) c.getB()).getLiteral() != 0) {
return;
}
InsnNode wrapInsn = ((InsnWrapArg) c.getA()).getWrapInsn();
InsnType type = wrapInsn.getType();
if (type != InsnType.CMP_L && type != InsnType.CMP_G) {
return;
}
IfNode insn = c.getInsn();
insn.changeCondition(insn.getOp(), wrapInsn.getArg(0), wrapInsn.getArg(1));
}
public List<RegisterArg> getRegisterArgs() {
List<RegisterArg> list = new LinkedList<RegisterArg>();
if (mode == Mode.COMPARE) {
@@ -184,8 +204,8 @@ public final class IfCondition {
if (a.isRegister()) {
list.add((RegisterArg) a);
}
InsnArg b = compare.getA();
if (a.isRegister()) {
InsnArg b = compare.getB();
if (b.isRegister()) {
list.add((RegisterArg) b);
}
} else {

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