Compare commits
50 Commits
v0.5.0-beta1
...
v0.5.0
| Author | SHA1 | Date | |
|---|---|---|---|
| 18a1788d2d | |||
| d0aa19118b | |||
| 039f6eebda | |||
| 8a464e8274 | |||
| 066b5a895d | |||
| 4c4af7928e | |||
| a0d8d9fcc6 | |||
| a2142b2ff8 | |||
| 5ed5ec5f7d | |||
| 95795620d5 | |||
| 890c0a9909 | |||
| b73cb40690 | |||
| ca448fc4d8 | |||
| 7a51c0d087 | |||
| 8762125bbf | |||
| 3d0c6e49ab | |||
| 03da35b29e | |||
| 3ccab60f43 | |||
| ed64b8c121 | |||
| 2a60ac47fe | |||
| 9cd72fe1e9 | |||
| 476b2c3735 | |||
| 5258c8363a | |||
| eb6d145dca | |||
| 63c003a02d | |||
| 5557fd814b | |||
| b1dc26ee06 | |||
| 56c0a588de | |||
| 47d65fcd87 | |||
| 85ab095630 | |||
| 1b5f0f6af6 | |||
| 2cf28eb2e7 | |||
| 2b300341a0 | |||
| 01fabca358 | |||
| 4ace552a27 | |||
| b61daaed33 | |||
| c6f0c89cf6 | |||
| 3c84975a09 | |||
| bb4ef4f0a2 | |||
| fd00330e6e | |||
| d10efec1ab | |||
| 3f08c99f19 | |||
| e3606d1b53 | |||
| ab593e3cd9 | |||
| 4a0aacf104 | |||
| 917cf20d37 | |||
| dabaeed8df | |||
| 4923b36e70 | |||
| ebf06fde65 | |||
| 438b3b50d9 |
+9
-6
@@ -1,9 +1,11 @@
|
||||
ext.jadxVersion = file('version').readLines().get(0)
|
||||
version = jadxVersion
|
||||
|
||||
apply plugin: 'sonar-runner'
|
||||
|
||||
subprojects {
|
||||
apply plugin: 'java'
|
||||
apply plugin: 'jacoco'
|
||||
apply plugin: 'idea'
|
||||
apply plugin: 'eclipse'
|
||||
|
||||
@@ -14,8 +16,8 @@ subprojects {
|
||||
|
||||
gradle.projectsEvaluated {
|
||||
tasks.withType(Compile) {
|
||||
if (!"${it}".contains(":jadx-samples:")) {
|
||||
options.compilerArgs << "-Xlint" << "-Xlint:unchecked" << "-Xlint:deprecation"
|
||||
if (!"${it}".contains(':jadx-samples:')) {
|
||||
options.compilerArgs << '-Xlint' << '-Xlint:unchecked' << '-Xlint:deprecation'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,14 +25,15 @@ subprojects {
|
||||
jar {
|
||||
version = jadxVersion
|
||||
manifest {
|
||||
mainAttributes('jadx-version' : jadxVersion)
|
||||
mainAttributes('jadx-version': jadxVersion)
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile 'org.slf4j:slf4j-api:1.7.5'
|
||||
compile 'org.slf4j:slf4j-api:1.7.6'
|
||||
testCompile 'junit:junit:4.11'
|
||||
testCompile "org.mockito:mockito-core:1.9.5"
|
||||
testCompile 'org.mockito:mockito-core:1.9.5'
|
||||
testCompile 'ch.qos.logback:logback-classic:1.1.1'
|
||||
}
|
||||
|
||||
repositories {
|
||||
@@ -66,5 +69,5 @@ task clean(type: Delete) {
|
||||
}
|
||||
|
||||
task wrapper(type: Wrapper) {
|
||||
gradleVersion = '1.9'
|
||||
gradleVersion = '1.11'
|
||||
}
|
||||
|
||||
Vendored
BIN
Binary file not shown.
+1
-1
@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=http\://services.gradle.org/distributions/gradle-1.9-bin.zip
|
||||
distributionUrl=http\://services.gradle.org/distributions/gradle-1.11-all.zip
|
||||
|
||||
@@ -6,6 +6,7 @@ applicationName = 'jadx'
|
||||
dependencies {
|
||||
compile(project(':jadx-core'))
|
||||
compile 'com.beust:jcommander:1.30'
|
||||
compile 'ch.qos.logback:logback-classic:1.1.1'
|
||||
}
|
||||
|
||||
startScripts {
|
||||
|
||||
@@ -68,25 +68,25 @@ public final class JadxCLIArgs implements IJadxArgs {
|
||||
System.exit(0);
|
||||
}
|
||||
try {
|
||||
if (threadsCount <= 0)
|
||||
if (threadsCount <= 0) {
|
||||
throw new JadxException("Threads count must be positive");
|
||||
|
||||
}
|
||||
if (files != null) {
|
||||
for (String fileName : files) {
|
||||
File file = new File(fileName);
|
||||
if (file.exists())
|
||||
if (file.exists()) {
|
||||
input.add(file);
|
||||
else
|
||||
} else {
|
||||
throw new JadxException("File not found: " + file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (input.size() > 1)
|
||||
if (input.size() > 1) {
|
||||
throw new JadxException("Only one input file is supported");
|
||||
|
||||
if (outDirName != null)
|
||||
}
|
||||
if (outDirName != null) {
|
||||
outputDir = new File(outDirName);
|
||||
|
||||
}
|
||||
if (isVerbose()) {
|
||||
ch.qos.logback.classic.Logger rootLogger =
|
||||
(ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
|
||||
@@ -114,8 +114,9 @@ public final class JadxCLIArgs implements IJadxArgs {
|
||||
int maxNamesLen = 0;
|
||||
for (ParameterDescription p : params) {
|
||||
int len = p.getNames().length();
|
||||
if (len > maxNamesLen)
|
||||
if (len > maxNamesLen) {
|
||||
maxNamesLen = len;
|
||||
}
|
||||
}
|
||||
|
||||
Field[] fields = this.getClass().getDeclaredFields();
|
||||
@@ -137,8 +138,9 @@ public final class JadxCLIArgs implements IJadxArgs {
|
||||
}
|
||||
|
||||
private static void addSpaces(StringBuilder str, int count) {
|
||||
for (int i = 0; i < count; i++)
|
||||
for (int i = 0; i < count; i++) {
|
||||
str.append(' ');
|
||||
}
|
||||
}
|
||||
|
||||
public List<File> getInput() {
|
||||
|
||||
@@ -2,7 +2,6 @@ ext.jadxClasspath = 'clsp-data/android-4.3.jar'
|
||||
|
||||
dependencies {
|
||||
compile 'com.google.android.tools:dx:1.7'
|
||||
compile 'ch.qos.logback:logback-classic:1.0.13'
|
||||
|
||||
runtime files(jadxClasspath)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
package jadx.api;
|
||||
|
||||
public final class CodePosition {
|
||||
|
||||
private final JavaClass cls;
|
||||
private final int line;
|
||||
private final int offset;
|
||||
|
||||
public CodePosition(JavaClass cls, int line, int offset) {
|
||||
this.cls = cls;
|
||||
this.line = line;
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
public CodePosition(int line, int offset) {
|
||||
this.cls = null;
|
||||
this.line = line;
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
public JavaClass getJavaClass() {
|
||||
return cls;
|
||||
}
|
||||
|
||||
public int getLine() {
|
||||
return line;
|
||||
}
|
||||
|
||||
public int getOffset() {
|
||||
return offset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
CodePosition that = (CodePosition) o;
|
||||
return line == that.line && offset == that.offset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return line + 31 * offset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return line + ":" + offset + (cls != null ? " " + cls : "");
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,6 @@ import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.dex.visitors.IDexTreeVisitor;
|
||||
import jadx.core.dex.visitors.SaveCode;
|
||||
import jadx.core.utils.ErrorsCounter;
|
||||
import jadx.core.utils.exceptions.CodegenException;
|
||||
import jadx.core.utils.exceptions.DecodeException;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
import jadx.core.utils.files.InputFile;
|
||||
@@ -16,7 +15,6 @@ import jadx.core.utils.files.InputFile;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
@@ -56,6 +54,7 @@ public final class Decompiler {
|
||||
|
||||
private RootNode root;
|
||||
private List<IDexTreeVisitor> passes;
|
||||
private List<JavaClass> classes;
|
||||
|
||||
public Decompiler() {
|
||||
this.args = new DefaultJadxArgs();
|
||||
@@ -80,7 +79,7 @@ public final class Decompiler {
|
||||
}
|
||||
|
||||
public void loadFile(File file) throws IOException, DecodeException {
|
||||
loadFiles(Arrays.asList(file));
|
||||
loadFiles(Collections.singletonList(file));
|
||||
}
|
||||
|
||||
public void loadFiles(List<File> files) throws IOException, DecodeException {
|
||||
@@ -97,6 +96,7 @@ public final class Decompiler {
|
||||
public void save() {
|
||||
try {
|
||||
ExecutorService ex = getSaveExecutor();
|
||||
ex.shutdown();
|
||||
ex.awaitTermination(1, TimeUnit.DAYS);
|
||||
} catch (InterruptedException e) {
|
||||
LOG.error("Save interrupted", e);
|
||||
@@ -110,41 +110,38 @@ public final class Decompiler {
|
||||
int threadsCount = args.getThreadsCount();
|
||||
LOG.debug("processing threads count: {}", threadsCount);
|
||||
|
||||
ArrayList<IDexTreeVisitor> passList = new ArrayList<IDexTreeVisitor>(passes);
|
||||
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 (ClassNode cls : root.getClasses(false)) {
|
||||
if (cls.getCode() == null) {
|
||||
ProcessClass job = new ProcessClass(cls, passList);
|
||||
executor.execute(job);
|
||||
} else {
|
||||
try {
|
||||
savePass.visit(cls);
|
||||
} catch (CodegenException e) {
|
||||
LOG.error("Can't save class {}", cls, e);
|
||||
for (final ClassNode cls : root.getClasses(false)) {
|
||||
executor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
ProcessClass.process(cls, passList);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
executor.shutdown();
|
||||
return executor;
|
||||
}
|
||||
|
||||
public List<JavaClass> getClasses() {
|
||||
List<ClassNode> classNodeList = root.getClasses(false);
|
||||
List<JavaClass> classes = new ArrayList<JavaClass>(classNodeList.size());
|
||||
for (ClassNode classNode : classNodeList) {
|
||||
classes.add(new JavaClass(this, classNode));
|
||||
if (classes == null) {
|
||||
List<ClassNode> classNodeList = root.getClasses(false);
|
||||
List<JavaClass> clsList = new ArrayList<JavaClass>(classNodeList.size());
|
||||
for (ClassNode classNode : classNodeList) {
|
||||
clsList.add(new JavaClass(this, classNode));
|
||||
}
|
||||
classes = Collections.unmodifiableList(clsList);
|
||||
}
|
||||
return Collections.unmodifiableList(classes);
|
||||
return classes;
|
||||
}
|
||||
|
||||
public List<JavaPackage> getPackages() {
|
||||
List<JavaClass> classes = getClasses();
|
||||
Map<String, List<JavaClass>> map = new HashMap<String, List<JavaClass>>();
|
||||
for (JavaClass javaClass : classes) {
|
||||
for (JavaClass javaClass : getClasses()) {
|
||||
String pkg = javaClass.getPackage();
|
||||
List<JavaClass> clsList = map.get(pkg);
|
||||
if (clsList == null) {
|
||||
@@ -174,25 +171,36 @@ public final class Decompiler {
|
||||
}
|
||||
|
||||
void parse() throws DecodeException {
|
||||
ClassInfo.clearCache();
|
||||
ErrorsCounter.reset();
|
||||
|
||||
reset();
|
||||
root = new RootNode();
|
||||
LOG.info("loading ...");
|
||||
root.load(inputFiles);
|
||||
}
|
||||
|
||||
private void reset() {
|
||||
ClassInfo.clearCache();
|
||||
ErrorsCounter.reset();
|
||||
classes = null;
|
||||
}
|
||||
|
||||
void processClass(ClassNode cls) {
|
||||
try {
|
||||
ProcessClass job = new ProcessClass(cls, passes);
|
||||
LOG.info("processing class {} ...", cls);
|
||||
job.run();
|
||||
} catch (Throwable e) {
|
||||
LOG.error("Process class error", e);
|
||||
}
|
||||
LOG.info("processing class {} ...", cls);
|
||||
ProcessClass.process(cls, passes);
|
||||
}
|
||||
|
||||
RootNode getRoot() {
|
||||
return root;
|
||||
}
|
||||
|
||||
JavaClass findJavaClass(ClassNode cls) {
|
||||
if (cls == null) {
|
||||
return null;
|
||||
}
|
||||
for (JavaClass javaClass : getClasses()) {
|
||||
if (javaClass.getClassNode().equals(cls)) {
|
||||
return javaClass;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,58 +1,88 @@
|
||||
package jadx.api;
|
||||
|
||||
import jadx.core.codegen.CodeWriter;
|
||||
import jadx.core.dex.attributes.AttributeFlag;
|
||||
import jadx.core.dex.attributes.LineAttrNode;
|
||||
import jadx.core.dex.info.AccessInfo;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public final class JavaClass {
|
||||
|
||||
private final Decompiler decompiler;
|
||||
private final ClassNode cls;
|
||||
private final List<JavaClass> innerClasses;
|
||||
private final List<JavaField> fields;
|
||||
private final List<JavaMethod> methods;
|
||||
|
||||
private List<JavaClass> innerClasses = Collections.emptyList();
|
||||
private List<JavaField> fields = Collections.emptyList();
|
||||
private List<JavaMethod> methods = Collections.emptyList();
|
||||
|
||||
JavaClass(Decompiler decompiler, ClassNode classNode) {
|
||||
this.decompiler = decompiler;
|
||||
this.cls = classNode;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
CodeWriter code = cls.getCode();
|
||||
if (code == null) {
|
||||
decompile();
|
||||
code = cls.getCode();
|
||||
}
|
||||
return code != null ? code.toString() : "error processing class";
|
||||
}
|
||||
|
||||
public void decompile() {
|
||||
if (decompiler == null) {
|
||||
throw new JadxRuntimeException("Can't decompile inner class");
|
||||
}
|
||||
if (cls.getCode() == null) {
|
||||
decompiler.processClass(cls);
|
||||
load();
|
||||
}
|
||||
}
|
||||
|
||||
ClassNode getClassNode() {
|
||||
return cls;
|
||||
}
|
||||
|
||||
private void load() {
|
||||
int inClsCount = cls.getInnerClasses().size();
|
||||
if (inClsCount == 0) {
|
||||
this.innerClasses = Collections.emptyList();
|
||||
} else {
|
||||
if (inClsCount != 0) {
|
||||
List<JavaClass> list = new ArrayList<JavaClass>(inClsCount);
|
||||
for (ClassNode inner : cls.getInnerClasses()) {
|
||||
list.add(new JavaClass(decompiler, inner));
|
||||
if (!inner.getAttributes().contains(AttributeFlag.DONT_GENERATE)) {
|
||||
JavaClass javaClass = new JavaClass(null, inner);
|
||||
javaClass.load();
|
||||
list.add(javaClass);
|
||||
}
|
||||
}
|
||||
this.innerClasses = Collections.unmodifiableList(list);
|
||||
}
|
||||
|
||||
int fieldsCount = cls.getFields().size();
|
||||
if (fieldsCount == 0) {
|
||||
this.fields = Collections.emptyList();
|
||||
} else {
|
||||
if (fieldsCount != 0) {
|
||||
List<JavaField> flds = new ArrayList<JavaField>(fieldsCount);
|
||||
for (FieldNode f : cls.getFields()) {
|
||||
flds.add(new JavaField(f));
|
||||
if (!f.getAttributes().contains(AttributeFlag.DONT_GENERATE)) {
|
||||
flds.add(new JavaField(f));
|
||||
}
|
||||
}
|
||||
this.fields = Collections.unmodifiableList(flds);
|
||||
}
|
||||
|
||||
int methodsCount = cls.getMethods().size();
|
||||
if (methodsCount == 0) {
|
||||
this.methods = Collections.emptyList();
|
||||
} else {
|
||||
if (methodsCount != 0) {
|
||||
List<JavaMethod> mths = new ArrayList<JavaMethod>(methodsCount);
|
||||
for (MethodNode m : cls.getMethods()) {
|
||||
if (!m.getAccessFlags().isSynthetic()) {
|
||||
mths.add(new JavaMethod(m));
|
||||
if (!m.getAttributes().contains(AttributeFlag.DONT_GENERATE)) {
|
||||
mths.add(new JavaMethod(this, m));
|
||||
}
|
||||
}
|
||||
Collections.sort(mths, new Comparator<JavaMethod>() {
|
||||
@@ -65,13 +95,36 @@ public final class JavaClass {
|
||||
}
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
CodeWriter code = cls.getCode();
|
||||
if (code == null) {
|
||||
decompiler.processClass(cls);
|
||||
code = cls.getCode();
|
||||
private Map<CodePosition, Object> getCodeAnnotations() {
|
||||
decompile();
|
||||
return cls.getCode().getAnnotations();
|
||||
}
|
||||
|
||||
public CodePosition getDefinitionPosition(int line, int offset) {
|
||||
Map<CodePosition, Object> map = getCodeAnnotations();
|
||||
Object obj = map.get(new CodePosition(line, offset));
|
||||
if (obj instanceof LineAttrNode) {
|
||||
ClassNode clsNode = null;
|
||||
if (obj instanceof ClassNode) {
|
||||
clsNode = (ClassNode) obj;
|
||||
} else if (obj instanceof MethodNode) {
|
||||
clsNode = ((MethodNode) obj).getParentClass();
|
||||
} else if (obj instanceof FieldNode) {
|
||||
clsNode = ((FieldNode) obj).getParentClass();
|
||||
}
|
||||
if (clsNode == null) {
|
||||
return null;
|
||||
}
|
||||
clsNode = clsNode.getParentClass();
|
||||
JavaClass jCls = decompiler.findJavaClass(clsNode);
|
||||
if (jCls == null) {
|
||||
return null;
|
||||
}
|
||||
jCls.decompile();
|
||||
int defLine = ((LineAttrNode) obj).getDecompiledLine();
|
||||
return new CodePosition(jCls, defLine, 0);
|
||||
}
|
||||
return code != null ? code.toString() : "error processing class";
|
||||
return null;
|
||||
}
|
||||
|
||||
public String getFullName() {
|
||||
@@ -102,12 +155,22 @@ public final class JavaClass {
|
||||
return methods;
|
||||
}
|
||||
|
||||
public int getDecompiledLine() {
|
||||
return cls.getDecompiledLine();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return this == o || o instanceof JavaClass && cls.equals(((JavaClass) o).cls);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return cls.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getFullName();
|
||||
}
|
||||
|
||||
public int getDecompiledLine() {
|
||||
return cls.getDecompiledLine();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package jadx.api;
|
||||
|
||||
import jadx.core.dex.info.AccessInfo;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
|
||||
@@ -9,19 +8,19 @@ import java.util.List;
|
||||
|
||||
public final class JavaMethod {
|
||||
private final MethodNode mth;
|
||||
private final JavaClass parent;
|
||||
|
||||
public JavaMethod(MethodNode m) {
|
||||
public JavaMethod(JavaClass cls, MethodNode m) {
|
||||
this.parent = cls;
|
||||
this.mth = m;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
MethodInfo mi = mth.getMethodInfo();
|
||||
if (mi.isConstructor()) {
|
||||
return mth.getParentClass().getShortName();
|
||||
} else if (mi.isClassInit()) {
|
||||
return "static";
|
||||
}
|
||||
return mi.getName();
|
||||
return mth.getMethodInfo().getName();
|
||||
}
|
||||
|
||||
public JavaClass getDeclaringClass() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
public AccessInfo getAccessFlags() {
|
||||
|
||||
@@ -26,11 +26,14 @@ public final class JavaPackage implements Comparable<JavaPackage> {
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
JavaPackage that = (JavaPackage) o;
|
||||
if (!name.equals(that.name)) return false;
|
||||
return true;
|
||||
return name.equals(that.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -21,4 +21,6 @@ public class Consts {
|
||||
|
||||
public static final String DEFAULT_PACKAGE_NAME = "defpackage";
|
||||
public static final String ANONYMOUS_CLASS_PREFIX = "AnonymousClass_";
|
||||
|
||||
public static final String MTH_TOSTRING_SIGNATURE = "toString()Ljava/lang/String;";
|
||||
}
|
||||
|
||||
@@ -10,12 +10,12 @@ import jadx.core.dex.visitors.DotGraphVisitor;
|
||||
import jadx.core.dex.visitors.EnumVisitor;
|
||||
import jadx.core.dex.visitors.FallbackModeVisitor;
|
||||
import jadx.core.dex.visitors.IDexTreeVisitor;
|
||||
import jadx.core.dex.visitors.MethodInlinerVisitor;
|
||||
import jadx.core.dex.visitors.MethodInlineVisitor;
|
||||
import jadx.core.dex.visitors.ModVisitor;
|
||||
import jadx.core.dex.visitors.PrepareForCodeGen;
|
||||
import jadx.core.dex.visitors.SimplifyVisitor;
|
||||
import jadx.core.dex.visitors.regions.CheckRegions;
|
||||
import jadx.core.dex.visitors.regions.CleanRegions;
|
||||
import jadx.core.dex.visitors.regions.PostRegionVisitor;
|
||||
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;
|
||||
@@ -37,10 +37,12 @@ public class Jadx {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(Jadx.class);
|
||||
|
||||
static {
|
||||
if (Consts.DEBUG)
|
||||
if (Consts.DEBUG) {
|
||||
LOG.info("debug enabled");
|
||||
if (Jadx.class.desiredAssertionStatus())
|
||||
}
|
||||
if (Jadx.class.desiredAssertionStatus()) {
|
||||
LOG.info("assertions enabled");
|
||||
}
|
||||
}
|
||||
|
||||
public static List<IDexTreeVisitor> getPassesList(IJadxArgs args, File outDir) {
|
||||
@@ -52,8 +54,9 @@ public class Jadx {
|
||||
|
||||
passes.add(new TypeResolver());
|
||||
|
||||
if (args.isRawCFGOutput())
|
||||
if (args.isRawCFGOutput()) {
|
||||
passes.add(new DotGraphVisitor(outDir, false, true));
|
||||
}
|
||||
|
||||
passes.add(new ConstInlinerVisitor());
|
||||
passes.add(new FinishTypeResolver());
|
||||
@@ -61,22 +64,26 @@ public class Jadx {
|
||||
passes.add(new ModVisitor());
|
||||
passes.add(new EnumVisitor());
|
||||
|
||||
if (args.isCFGOutput())
|
||||
if (args.isCFGOutput()) {
|
||||
passes.add(new DotGraphVisitor(outDir, false));
|
||||
}
|
||||
|
||||
passes.add(new CodeShrinker());
|
||||
passes.add(new RegionMakerVisitor());
|
||||
passes.add(new PostRegionVisitor());
|
||||
passes.add(new IfRegionVisitor());
|
||||
|
||||
passes.add(new CodeShrinker());
|
||||
passes.add(new SimplifyVisitor());
|
||||
passes.add(new ProcessVariables());
|
||||
passes.add(new CheckRegions());
|
||||
if (args.isCFGOutput())
|
||||
passes.add(new DotGraphVisitor(outDir, true));
|
||||
|
||||
passes.add(new MethodInlinerVisitor());
|
||||
if (args.isCFGOutput()) {
|
||||
passes.add(new DotGraphVisitor(outDir, true));
|
||||
}
|
||||
|
||||
passes.add(new MethodInlineVisitor());
|
||||
passes.add(new ClassModifier());
|
||||
passes.add(new CleanRegions());
|
||||
passes.add(new PrepareForCodeGen());
|
||||
}
|
||||
passes.add(new CodeGen(args));
|
||||
return passes;
|
||||
@@ -88,8 +95,9 @@ public class Jadx {
|
||||
while (resources.hasMoreElements()) {
|
||||
Manifest manifest = new Manifest(resources.nextElement().openStream());
|
||||
String ver = manifest.getMainAttributes().getValue("jadx-version");
|
||||
if (ver != null)
|
||||
if (ver != null) {
|
||||
return ver;
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.error("Can't get manifest file", e);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package jadx.core;
|
||||
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.visitors.DepthTraverser;
|
||||
import jadx.core.dex.visitors.DepthTraversal;
|
||||
import jadx.core.dex.visitors.IDexTreeVisitor;
|
||||
import jadx.core.utils.exceptions.DecodeException;
|
||||
|
||||
@@ -10,26 +10,22 @@ import java.util.List;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public final class ProcessClass implements Runnable {
|
||||
public final class ProcessClass {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ProcessClass.class);
|
||||
|
||||
private final ClassNode cls;
|
||||
private final List<IDexTreeVisitor> passes;
|
||||
|
||||
public ProcessClass(ClassNode cls, List<IDexTreeVisitor> passes) {
|
||||
this.cls = cls;
|
||||
this.passes = passes;
|
||||
private ProcessClass() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
public static void process(ClassNode cls, List<IDexTreeVisitor> passes) {
|
||||
try {
|
||||
cls.load();
|
||||
for (IDexTreeVisitor visitor : passes) {
|
||||
DepthTraverser.visit(visitor, cls);
|
||||
DepthTraversal.visit(visitor, cls);
|
||||
}
|
||||
} catch (DecodeException e) {
|
||||
LOG.error("Decode exception: " + cls, e);
|
||||
} catch (Exception e) {
|
||||
LOG.error("Class process exception: " + cls, e);
|
||||
} finally {
|
||||
cls.unload();
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import jadx.core.utils.exceptions.DecodeException;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
@@ -44,35 +45,47 @@ public class ClspGraph {
|
||||
throw new JadxRuntimeException("Classpath must be loaded first");
|
||||
}
|
||||
int size = classes.size();
|
||||
for (ClassNode cls : classes) {
|
||||
size += cls.getInnerClasses().size();
|
||||
}
|
||||
NClass[] nClasses = new NClass[size];
|
||||
for (int i = 0; i < size; i++) {
|
||||
ClassNode cls = classes.get(i);
|
||||
NClass nClass = new NClass(cls.getRawName(), -1);
|
||||
nClasses[i] = nClass;
|
||||
nameMap.put(cls.getRawName(), nClass);
|
||||
int k = 0;
|
||||
for (ClassNode cls : classes) {
|
||||
nClasses[k++] = addClass(cls);
|
||||
for (ClassNode inner : cls.getInnerClasses()) {
|
||||
nClasses[k++] = addClass(inner);
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < size; i++) {
|
||||
nClasses[i].setParents(ClsSet.makeParentsArray(classes.get(i), nameMap));
|
||||
}
|
||||
}
|
||||
|
||||
private NClass addClass(ClassNode cls) {
|
||||
NClass nClass = new NClass(cls.getRawName(), -1);
|
||||
nameMap.put(cls.getRawName(), nClass);
|
||||
return nClass;
|
||||
}
|
||||
|
||||
public boolean isImplements(String clsName, String implClsName) {
|
||||
Set<String> anc = getAncestors(clsName);
|
||||
return anc.contains(implClsName);
|
||||
}
|
||||
|
||||
public String getCommonAncestor(String clsName, String implClsName) {
|
||||
if (isImplements(clsName, implClsName)) {
|
||||
return implClsName;
|
||||
if (clsName.equals(implClsName)) {
|
||||
return clsName;
|
||||
}
|
||||
Set<String> anc = getAncestors(clsName);
|
||||
NClass cls = nameMap.get(implClsName);
|
||||
if (cls != null) {
|
||||
if (isImplements(clsName, implClsName)) {
|
||||
return implClsName;
|
||||
}
|
||||
Set<String> anc = getAncestors(clsName);
|
||||
return searchCommonParent(anc, cls);
|
||||
} else {
|
||||
LOG.debug("Missing class: {}", implClsName);
|
||||
return null;
|
||||
}
|
||||
LOG.debug("Missing class: {}", implClsName);
|
||||
return null;
|
||||
}
|
||||
|
||||
private String searchCommonParent(Set<String> anc, NClass cls) {
|
||||
@@ -82,8 +95,9 @@ public class ClspGraph {
|
||||
return name;
|
||||
} else {
|
||||
String r = searchCommonParent(anc, p);
|
||||
if (r != null)
|
||||
if (r != null) {
|
||||
return r;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
@@ -91,17 +105,21 @@ public class ClspGraph {
|
||||
|
||||
private Set<String> getAncestors(String clsName) {
|
||||
Set<String> result = ancestorCache.get(clsName);
|
||||
if (result == null) {
|
||||
result = new HashSet<String>();
|
||||
ancestorCache.put(clsName, result);
|
||||
NClass cls = nameMap.get(clsName);
|
||||
if (cls != null) {
|
||||
addAncestorsNames(cls, result);
|
||||
} else {
|
||||
LOG.debug("Missing class: {}", clsName);
|
||||
}
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
return result;
|
||||
NClass cls = nameMap.get(clsName);
|
||||
if (cls != null) {
|
||||
result = new HashSet<String>();
|
||||
addAncestorsNames(cls, result);
|
||||
if (result.isEmpty()) {
|
||||
result = Collections.emptySet();
|
||||
}
|
||||
ancestorCache.put(clsName, result);
|
||||
return result;
|
||||
}
|
||||
LOG.debug("Missing class: {}", clsName);
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
private void addAncestorsNames(NClass cls, Set<String> result) {
|
||||
|
||||
@@ -41,11 +41,14 @@ public class NClass {
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
NClass nClass = (NClass) o;
|
||||
if (!name.equals(nClass.name)) return false;
|
||||
return true;
|
||||
return name.equals(nClass.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -43,20 +43,20 @@ public class AnnotationGen {
|
||||
|
||||
public void addForParameter(CodeWriter code, MethodParameters paramsAnnotations, int n) {
|
||||
AnnotationsList aList = paramsAnnotations.getParamList().get(n);
|
||||
if (aList == null || aList.size() == 0)
|
||||
if (aList == null || aList.size() == 0) {
|
||||
return;
|
||||
|
||||
}
|
||||
for (Annotation a : aList.getAll()) {
|
||||
code.add(formatAnnotation(a));
|
||||
formatAnnotation(code, a);
|
||||
code.add(' ');
|
||||
}
|
||||
}
|
||||
|
||||
private void add(IAttributeNode node, CodeWriter code) {
|
||||
AnnotationsList aList = (AnnotationsList) node.getAttributes().get(AttributeType.ANNOTATION_LIST);
|
||||
if (aList == null || aList.size() == 0)
|
||||
if (aList == null || aList.size() == 0) {
|
||||
return;
|
||||
|
||||
}
|
||||
for (Annotation a : aList.getAll()) {
|
||||
String aCls = a.getAnnotationClass();
|
||||
if (aCls.startsWith(Consts.DALVIK_ANNOTATION_PKG)) {
|
||||
@@ -66,33 +66,32 @@ public class AnnotationGen {
|
||||
}
|
||||
} else {
|
||||
code.startLine();
|
||||
code.add(formatAnnotation(a));
|
||||
formatAnnotation(code, a);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private CodeWriter formatAnnotation(Annotation a) {
|
||||
CodeWriter code = new CodeWriter();
|
||||
private void formatAnnotation(CodeWriter code, Annotation a) {
|
||||
code.add('@');
|
||||
code.add(classGen.useClass(a.getType()));
|
||||
Map<String, Object> vl = a.getValues();
|
||||
if (vl.size() != 0) {
|
||||
if (!vl.isEmpty()) {
|
||||
code.add('(');
|
||||
if (vl.size() == 1 && vl.containsKey("value")) {
|
||||
code.add(encValueToString(vl.get("value")));
|
||||
encodeValue(code, vl.get("value"));
|
||||
} else {
|
||||
for (Iterator<Entry<String, Object>> it = vl.entrySet().iterator(); it.hasNext(); ) {
|
||||
Entry<String, Object> e = it.next();
|
||||
code.add(e.getKey());
|
||||
code.add(" = ");
|
||||
code.add(encValueToString(e.getValue()));
|
||||
if (it.hasNext())
|
||||
encodeValue(code, e.getValue());
|
||||
if (it.hasNext()) {
|
||||
code.add(", ");
|
||||
}
|
||||
}
|
||||
}
|
||||
code.add(')');
|
||||
}
|
||||
return code;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@@ -104,8 +103,9 @@ public class AnnotationGen {
|
||||
for (Iterator<ArgType> it = ((List<ArgType>) exs).iterator(); it.hasNext(); ) {
|
||||
ArgType ex = it.next();
|
||||
code.add(TypeGen.translate(classGen, ex));
|
||||
if (it.hasNext())
|
||||
if (it.hasNext()) {
|
||||
code.add(", ");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -120,65 +120,51 @@ public class AnnotationGen {
|
||||
}
|
||||
|
||||
// TODO: refactor this boilerplate code
|
||||
@SuppressWarnings("unchecked")
|
||||
public String encValueToString(Object val) {
|
||||
if (val == null)
|
||||
return "null";
|
||||
|
||||
if (val instanceof String)
|
||||
return StringUtils.unescapeString((String) val);
|
||||
if (val instanceof Integer)
|
||||
return TypeGen.formatInteger((Integer) val);
|
||||
if (val instanceof Character)
|
||||
return StringUtils.unescapeChar((Character) val);
|
||||
if (val instanceof Boolean)
|
||||
return Boolean.TRUE.equals(val) ? "true" : "false";
|
||||
if (val instanceof Float)
|
||||
return TypeGen.formatFloat((Float) val);
|
||||
if (val instanceof Double)
|
||||
return TypeGen.formatDouble((Double) val);
|
||||
if (val instanceof Long)
|
||||
return TypeGen.formatLong((Long) val);
|
||||
if (val instanceof Short)
|
||||
return TypeGen.formatShort((Short) val);
|
||||
if (val instanceof Byte)
|
||||
return TypeGen.formatByte((Byte) val);
|
||||
|
||||
if (val instanceof ArgType)
|
||||
return TypeGen.translate(classGen, (ArgType) val) + ".class";
|
||||
|
||||
if (val instanceof FieldInfo) {
|
||||
public void encodeValue(CodeWriter code, Object val) {
|
||||
if (val == null) {
|
||||
code.add("null");
|
||||
return;
|
||||
}
|
||||
if (val instanceof String) {
|
||||
code.add(StringUtils.unescapeString((String) val));
|
||||
} else if (val instanceof Integer) {
|
||||
code.add(TypeGen.formatInteger((Integer) val));
|
||||
} else if (val instanceof Character) {
|
||||
code.add(StringUtils.unescapeChar((Character) val));
|
||||
} else if (val instanceof Boolean) {
|
||||
code.add(Boolean.TRUE.equals(val) ? "true" : "false");
|
||||
} else if (val instanceof Float) {
|
||||
code.add(TypeGen.formatFloat((Float) val));
|
||||
} else if (val instanceof Double) {
|
||||
code.add(TypeGen.formatDouble((Double) val));
|
||||
} else if (val instanceof Long) {
|
||||
code.add(TypeGen.formatLong((Long) val));
|
||||
} else if (val instanceof Short) {
|
||||
code.add(TypeGen.formatShort((Short) val));
|
||||
} else if (val instanceof Byte) {
|
||||
code.add(TypeGen.formatByte((Byte) val));
|
||||
} else if (val instanceof ArgType) {
|
||||
code.add(TypeGen.translate(classGen, (ArgType) val)).add(".class");
|
||||
} else if (val instanceof FieldInfo) {
|
||||
// must be a static field
|
||||
FieldInfo field = (FieldInfo) val;
|
||||
// FIXME: !!code from InsnGen.sfield
|
||||
String thisClass = cls.getFullName();
|
||||
if (field.getDeclClass().getFullName().equals(thisClass)) {
|
||||
return field.getName();
|
||||
} else {
|
||||
return classGen.useClass(field.getDeclClass()) + '.' + field.getName();
|
||||
}
|
||||
}
|
||||
|
||||
if (val instanceof List) {
|
||||
StringBuilder str = new StringBuilder();
|
||||
str.append('{');
|
||||
List<Object> list = (List<Object>) val;
|
||||
for (Iterator<Object> it = list.iterator(); it.hasNext(); ) {
|
||||
code.add(InsnGen.makeStaticFieldAccess(field, classGen));
|
||||
} else if (val instanceof List) {
|
||||
code.add('{');
|
||||
Iterator<?> it = ((List) val).iterator();
|
||||
while (it.hasNext()) {
|
||||
Object obj = it.next();
|
||||
str.append(encValueToString(obj));
|
||||
if (it.hasNext())
|
||||
str.append(", ");
|
||||
encodeValue(code, obj);
|
||||
if (it.hasNext()) {
|
||||
code.add(", ");
|
||||
}
|
||||
}
|
||||
str.append('}');
|
||||
return str.toString();
|
||||
code.add('}');
|
||||
} else if (val instanceof Annotation) {
|
||||
formatAnnotation(code, (Annotation) val);
|
||||
} else {
|
||||
// TODO: also can be method values
|
||||
throw new JadxRuntimeException("Can't decode value: " + val + " (" + val.getClass() + ")");
|
||||
}
|
||||
|
||||
if (val instanceof Annotation) {
|
||||
return formatAnnotation((Annotation) val).toString();
|
||||
}
|
||||
|
||||
// TODO: also can be method values
|
||||
|
||||
throw new JadxRuntimeException("Can't decode value: " + val + " (" + val.getClass() + ")");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import jadx.core.dex.attributes.AttributeFlag;
|
||||
import jadx.core.dex.attributes.AttributeType;
|
||||
import jadx.core.dex.attributes.EnumClassAttr;
|
||||
import jadx.core.dex.attributes.EnumClassAttr.EnumField;
|
||||
import jadx.core.dex.attributes.IAttribute;
|
||||
import jadx.core.dex.attributes.SourceFileAttr;
|
||||
import jadx.core.dex.info.AccessInfo;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
@@ -44,6 +43,7 @@ public class ClassGen {
|
||||
private final boolean fallback;
|
||||
|
||||
private final Set<ClassInfo> imports = new HashSet<ClassInfo>();
|
||||
private int clsDeclLine = 0;
|
||||
|
||||
public ClassGen(ClassNode cls, ClassGen parentClsGen, boolean fallback) {
|
||||
this.cls = cls;
|
||||
@@ -90,18 +90,17 @@ public class ClassGen {
|
||||
}
|
||||
|
||||
public void addClassCode(CodeWriter code) throws CodegenException {
|
||||
if (cls.getAttributes().contains(AttributeFlag.DONT_GENERATE))
|
||||
if (cls.getAttributes().contains(AttributeFlag.DONT_GENERATE)) {
|
||||
return;
|
||||
|
||||
if (cls.getAttributes().contains(AttributeFlag.INCONSISTENT_CODE))
|
||||
}
|
||||
if (cls.getAttributes().contains(AttributeFlag.INCONSISTENT_CODE)) {
|
||||
code.startLine("// jadx: inconsistent code");
|
||||
|
||||
makeClassDeclaration(code);
|
||||
makeClassBody(code);
|
||||
code.newLine();
|
||||
}
|
||||
addClassDeclaration(code);
|
||||
addClassBody(code);
|
||||
}
|
||||
|
||||
public void makeClassDeclaration(CodeWriter clsCode) {
|
||||
public void addClassDeclaration(CodeWriter clsCode) {
|
||||
AccessInfo af = cls.getAccessFlags();
|
||||
if (af.isInterface()) {
|
||||
af = af.remove(AccessFlags.ACC_ABSTRACT);
|
||||
@@ -113,8 +112,9 @@ public class ClassGen {
|
||||
insertSourceFileInfo(clsCode, cls);
|
||||
clsCode.startLine(af.makeString());
|
||||
if (af.isInterface()) {
|
||||
if (af.isAnnotation())
|
||||
if (af.isAnnotation()) {
|
||||
clsCode.add('@');
|
||||
}
|
||||
clsCode.add("interface ");
|
||||
} else if (af.isEnum()) {
|
||||
clsCode.add("enum ");
|
||||
@@ -123,7 +123,7 @@ public class ClassGen {
|
||||
}
|
||||
clsCode.add(cls.getShortName());
|
||||
|
||||
makeGenericMap(clsCode, cls.getGenericMap());
|
||||
addGenericMap(clsCode, cls.getGenericMap());
|
||||
clsCode.add(' ');
|
||||
|
||||
ClassInfo sup = cls.getSuperClass();
|
||||
@@ -134,28 +134,29 @@ public class ClassGen {
|
||||
}
|
||||
|
||||
if (cls.getInterfaces().size() > 0 && !af.isAnnotation()) {
|
||||
if (cls.getAccessFlags().isInterface())
|
||||
if (cls.getAccessFlags().isInterface()) {
|
||||
clsCode.add("extends ");
|
||||
else
|
||||
} else {
|
||||
clsCode.add("implements ");
|
||||
|
||||
}
|
||||
for (Iterator<ClassInfo> it = cls.getInterfaces().iterator(); it.hasNext(); ) {
|
||||
ClassInfo interf = it.next();
|
||||
clsCode.add(useClass(interf));
|
||||
if (it.hasNext())
|
||||
if (it.hasNext()) {
|
||||
clsCode.add(", ");
|
||||
}
|
||||
}
|
||||
if (!cls.getInterfaces().isEmpty())
|
||||
if (!cls.getInterfaces().isEmpty()) {
|
||||
clsCode.add(' ');
|
||||
}
|
||||
}
|
||||
|
||||
clsCode.attachAnnotation(cls);
|
||||
clsCode.attachDefinition(cls);
|
||||
}
|
||||
|
||||
public boolean makeGenericMap(CodeWriter code, Map<ArgType, List<ArgType>> gmap) {
|
||||
if (gmap == null || gmap.isEmpty())
|
||||
public boolean addGenericMap(CodeWriter code, Map<ArgType, List<ArgType>> gmap) {
|
||||
if (gmap == null || gmap.isEmpty()) {
|
||||
return false;
|
||||
|
||||
}
|
||||
code.add('<');
|
||||
int i = 0;
|
||||
for (Entry<ArgType, List<ArgType>> e : gmap.entrySet()) {
|
||||
@@ -181,124 +182,79 @@ public class ClassGen {
|
||||
return true;
|
||||
}
|
||||
|
||||
public void makeClassBody(CodeWriter clsCode) throws CodegenException {
|
||||
public void addClassBody(CodeWriter clsCode) throws CodegenException {
|
||||
clsCode.add('{');
|
||||
CodeWriter mthsCode = makeMethods(clsCode, cls.getMethods());
|
||||
CodeWriter fieldsCode = makeFields(clsCode, cls, cls.getFields());
|
||||
clsCode.add(fieldsCode);
|
||||
if (fieldsCode.notEmpty() && mthsCode.notEmpty())
|
||||
clsCode.newLine();
|
||||
|
||||
// insert inner classes code
|
||||
if (cls.getInnerClasses().size() != 0) {
|
||||
clsCode.add(makeInnerClasses(cls, clsCode.getIndent()));
|
||||
if (mthsCode.notEmpty())
|
||||
clsCode.newLine();
|
||||
}
|
||||
clsCode.add(mthsCode);
|
||||
clsDeclLine = clsCode.getLine();
|
||||
clsCode.incIndent();
|
||||
addFields(clsCode);
|
||||
addInnerClasses(clsCode, cls);
|
||||
addMethods(clsCode);
|
||||
clsCode.decIndent();
|
||||
clsCode.startLine('}');
|
||||
}
|
||||
|
||||
private CodeWriter makeInnerClasses(ClassNode cls, int indent) throws CodegenException {
|
||||
CodeWriter innerClsCode = new CodeWriter(indent + 1);
|
||||
for (ClassNode inCls : cls.getInnerClasses()) {
|
||||
if (inCls.isAnonymous())
|
||||
continue;
|
||||
|
||||
ClassGen inClGen = new ClassGen(inCls, parentGen == null ? this : parentGen, fallback);
|
||||
inClGen.addClassCode(innerClsCode);
|
||||
imports.addAll(inClGen.getImports());
|
||||
}
|
||||
return innerClsCode;
|
||||
}
|
||||
|
||||
private CodeWriter makeMethods(CodeWriter clsCode, List<MethodNode> mthList) {
|
||||
CodeWriter code = new CodeWriter(clsCode.getIndent() + 1);
|
||||
for (Iterator<MethodNode> it = mthList.iterator(); it.hasNext(); ) {
|
||||
MethodNode mth = it.next();
|
||||
if (mth.getAttributes().contains(AttributeFlag.DONT_GENERATE))
|
||||
continue;
|
||||
|
||||
try {
|
||||
if (mth.getAccessFlags().isAbstract() || mth.getAccessFlags().isNative()) {
|
||||
MethodGen mthGen = new MethodGen(this, mth);
|
||||
mthGen.addDefinition(code);
|
||||
if (cls.getAccessFlags().isAnnotation()) {
|
||||
Object def = annotationGen.getAnnotationDefaultValue(mth.getName());
|
||||
if (def != null) {
|
||||
String v = annotationGen.encValueToString(def);
|
||||
code.add(" default ").add(v);
|
||||
}
|
||||
}
|
||||
code.add(';');
|
||||
} else {
|
||||
if (mth.isNoCode())
|
||||
continue;
|
||||
|
||||
MethodGen mthGen = new MethodGen(this, mth);
|
||||
if (mth.getAttributes().contains(AttributeFlag.INCONSISTENT_CODE)) {
|
||||
code.startLine("/* JADX WARNING: inconsistent code */");
|
||||
LOG.error(ErrorsCounter.formatErrorMsg(mth, " Inconsistent code"));
|
||||
mthGen.makeMethodDump(code);
|
||||
}
|
||||
if (mthGen.addDefinition(code)) {
|
||||
code.add(' ');
|
||||
}
|
||||
code.add('{');
|
||||
insertSourceFileInfo(code, mth);
|
||||
code.add(mthGen.makeInstructions(code.getIndent()));
|
||||
code.startLine('}');
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
String msg = ErrorsCounter.methodError(mth, "Method generation error", e);
|
||||
code.startLine("/* " + msg + CodeWriter.NL + Utils.getStackTrace(e) + " */");
|
||||
}
|
||||
|
||||
if (it.hasNext())
|
||||
private void addInnerClasses(CodeWriter code, ClassNode cls) throws CodegenException {
|
||||
for (ClassNode innerCls : cls.getInnerClasses()) {
|
||||
if (!innerCls.isAnonymous()) {
|
||||
ClassGen inClGen = new ClassGen(innerCls, getParentGen(), fallback);
|
||||
code.newLine();
|
||||
inClGen.addClassCode(code);
|
||||
imports.addAll(inClGen.getImports());
|
||||
}
|
||||
}
|
||||
return code;
|
||||
}
|
||||
|
||||
private CodeWriter makeFields(CodeWriter clsCode, ClassNode cls, List<FieldNode> fields) throws CodegenException {
|
||||
CodeWriter code = new CodeWriter(clsCode.getIndent() + 1);
|
||||
|
||||
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, enumFields.getStaticMethod(), false);
|
||||
}
|
||||
code.add(igen.arg(arg));
|
||||
if (aIt.hasNext())
|
||||
code.add(", ");
|
||||
private void addMethods(CodeWriter code) {
|
||||
for (MethodNode mth : cls.getMethods()) {
|
||||
if (!mth.getAttributes().contains(AttributeFlag.DONT_GENERATE)) {
|
||||
try {
|
||||
if (code.getLine() != clsDeclLine) {
|
||||
code.newLine();
|
||||
}
|
||||
code.add(')');
|
||||
addMethod(code, mth);
|
||||
} catch (Exception e) {
|
||||
String msg = ErrorsCounter.methodError(mth, "Method generation error", e);
|
||||
code.startLine("/* " + msg + CodeWriter.NL + Utils.getStackTrace(e) + " */");
|
||||
}
|
||||
if (f.getCls() != null) {
|
||||
new ClassGen(f.getCls(), this, fallback).makeClassBody(code);
|
||||
}
|
||||
if (it.hasNext())
|
||||
code.add(',');
|
||||
}
|
||||
if (enumFields.getFields().isEmpty())
|
||||
code.startLine();
|
||||
|
||||
code.add(';');
|
||||
code.newLine();
|
||||
}
|
||||
}
|
||||
|
||||
for (FieldNode f : fields) {
|
||||
if(f.getAttributes().contains(AttributeFlag.DONT_GENERATE)) {
|
||||
private void addMethod(CodeWriter code, MethodNode mth) throws CodegenException {
|
||||
MethodGen mthGen = new MethodGen(this, mth);
|
||||
if (mth.getAccessFlags().isAbstract() || mth.getAccessFlags().isNative()) {
|
||||
mthGen.addDefinition(code);
|
||||
if (cls.getAccessFlags().isAnnotation()) {
|
||||
Object def = annotationGen.getAnnotationDefaultValue(mth.getName());
|
||||
if (def != null) {
|
||||
code.add(" default ");
|
||||
annotationGen.encodeValue(code, def);
|
||||
}
|
||||
}
|
||||
code.add(';');
|
||||
} else {
|
||||
boolean badCode = mth.getAttributes().contains(AttributeFlag.INCONSISTENT_CODE);
|
||||
if (badCode) {
|
||||
code.startLine("/* JADX WARNING: inconsistent code. */");
|
||||
code.startLine("/* Code decompiled incorrectly, please refer to instructions dump. */");
|
||||
LOG.error(ErrorsCounter.formatErrorMsg(mth, " Inconsistent code"));
|
||||
}
|
||||
if (mthGen.addDefinition(code)) {
|
||||
code.add(' ');
|
||||
}
|
||||
code.add('{');
|
||||
code.incIndent();
|
||||
insertSourceFileInfo(code, mth);
|
||||
mthGen.addInstructions(code);
|
||||
code.decIndent();
|
||||
code.startLine('}');
|
||||
}
|
||||
}
|
||||
|
||||
private void addFields(CodeWriter code) throws CodegenException {
|
||||
addEnumFields(code);
|
||||
for (FieldNode f : cls.getFields()) {
|
||||
if (f.getAttributes().contains(AttributeFlag.DONT_GENERATE)) {
|
||||
continue;
|
||||
}
|
||||
annotationGen.addForField(code, f);
|
||||
@@ -312,13 +268,50 @@ public class ClassGen {
|
||||
if (fv.getValue() == null) {
|
||||
code.add(TypeGen.literalToString(0, f.getType()));
|
||||
} else {
|
||||
code.add(annotationGen.encValueToString(fv.getValue()));
|
||||
annotationGen.encodeValue(code, fv.getValue());
|
||||
}
|
||||
}
|
||||
code.add(';');
|
||||
code.attachAnnotation(f);
|
||||
code.attachDefinition(f);
|
||||
}
|
||||
}
|
||||
|
||||
private void addEnumFields(CodeWriter code) throws CodegenException {
|
||||
EnumClassAttr enumFields = (EnumClassAttr) cls.getAttributes().get(AttributeType.ENUM_CLASS);
|
||||
if (enumFields != null) {
|
||||
InsnGen igen = null;
|
||||
for (Iterator<EnumField> it = enumFields.getFields().iterator(); it.hasNext(); ) {
|
||||
EnumField f = it.next();
|
||||
code.startLine(f.getName());
|
||||
if (f.getArgs().size() != 0) {
|
||||
code.add('(');
|
||||
for (Iterator<InsnArg> aIt = f.getArgs().iterator(); aIt.hasNext(); ) {
|
||||
InsnArg arg = aIt.next();
|
||||
if (igen == null) {
|
||||
// don't init mth gen if this is simple enum
|
||||
MethodGen mthGen = new MethodGen(this, enumFields.getStaticMethod());
|
||||
igen = new InsnGen(mthGen, false);
|
||||
}
|
||||
igen.addArg(code, arg);
|
||||
if (aIt.hasNext()) {
|
||||
code.add(", ");
|
||||
}
|
||||
}
|
||||
code.add(')');
|
||||
}
|
||||
if (f.getCls() != null) {
|
||||
new ClassGen(f.getCls(), this, fallback).addClassBody(code);
|
||||
}
|
||||
if (it.hasNext()) {
|
||||
code.add(',');
|
||||
}
|
||||
}
|
||||
if (enumFields.getFields().isEmpty()) {
|
||||
code.startLine();
|
||||
}
|
||||
code.add(';');
|
||||
code.newLine();
|
||||
}
|
||||
return code;
|
||||
}
|
||||
|
||||
public String useClass(ArgType clsType) {
|
||||
@@ -330,33 +323,41 @@ public class ClassGen {
|
||||
|
||||
public String useClass(ClassInfo classInfo) {
|
||||
String baseClass = useClassInternal(cls.getClassInfo(), classInfo);
|
||||
ArgType[] generics = classInfo.getType().getGenericTypes();
|
||||
if (generics != null) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(baseClass);
|
||||
sb.append('<');
|
||||
int len = generics.length;
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (i != 0) {
|
||||
sb.append(", ");
|
||||
}
|
||||
ArgType gt = generics[i];
|
||||
if (gt.isTypeKnown())
|
||||
sb.append(TypeGen.translate(this, gt));
|
||||
else
|
||||
sb.append('?');
|
||||
}
|
||||
sb.append('>');
|
||||
return sb.toString();
|
||||
} else {
|
||||
ArgType type = classInfo.getType();
|
||||
ArgType[] generics = type.getGenericTypes();
|
||||
if (generics == null) {
|
||||
return baseClass;
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(baseClass);
|
||||
sb.append('<');
|
||||
int len = generics.length;
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (i != 0) {
|
||||
sb.append(", ");
|
||||
}
|
||||
ArgType gt = generics[i];
|
||||
ArgType wt = gt.getWildcardType();
|
||||
if (wt != null) {
|
||||
sb.append('?');
|
||||
int bounds = gt.getWildcardBounds();
|
||||
if (bounds != 0) {
|
||||
sb.append(bounds == -1 ? " super " : " extends ");
|
||||
sb.append(TypeGen.translate(this, wt));
|
||||
}
|
||||
} else {
|
||||
sb.append(TypeGen.translate(this, gt));
|
||||
}
|
||||
}
|
||||
sb.append('>');
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private String useClassInternal(ClassInfo useCls, ClassInfo classInfo) {
|
||||
String clsStr = classInfo.getFullName();
|
||||
String fullName = classInfo.getFullName();
|
||||
if (fallback) {
|
||||
return clsStr;
|
||||
return fullName;
|
||||
}
|
||||
String shortName = classInfo.getShortName();
|
||||
if (classInfo.getPackage().equals("java.lang") && classInfo.getParentClass() == null) {
|
||||
@@ -370,16 +371,25 @@ public class ClassGen {
|
||||
if (classInfo.getPackage().equals(useCls.getPackage()) && !classInfo.isInner()) {
|
||||
return shortName;
|
||||
}
|
||||
if (classInfo.getPackage().equals(useCls.getPackage())) {
|
||||
clsStr = classInfo.getNameWithoutPackage();
|
||||
// don't add import if class not public (must be accessed using inheritance)
|
||||
ClassNode classNode = cls.dex().resolveClass(classInfo);
|
||||
if (classNode != null && !classNode.getAccessFlags().isPublic()) {
|
||||
return shortName;
|
||||
}
|
||||
if (searchCollision(cls.dex(), useCls, shortName)) {
|
||||
return clsStr;
|
||||
return fullName;
|
||||
}
|
||||
for (ClassInfo cls : imports) {
|
||||
if (!cls.equals(classInfo)) {
|
||||
if (cls.getShortName().equals(shortName)) {
|
||||
return clsStr;
|
||||
if (classInfo.getPackage().equals(useCls.getPackage())) {
|
||||
fullName = classInfo.getNameWithoutPackage();
|
||||
}
|
||||
for (ClassInfo importCls : getImports()) {
|
||||
if (!importCls.equals(classInfo)
|
||||
&& importCls.getShortName().equals(shortName)) {
|
||||
if (classInfo.isInner()) {
|
||||
String parent = useClassInternal(useCls, classInfo.getParentClass());
|
||||
return parent + "." + shortName;
|
||||
} else {
|
||||
return fullName;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -396,6 +406,14 @@ public class ClassGen {
|
||||
}
|
||||
}
|
||||
|
||||
private Set<ClassInfo> getImports() {
|
||||
if (parentGen != null) {
|
||||
return parentGen.getImports();
|
||||
} else {
|
||||
return imports;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isClassInnerFor(ClassInfo inner, ClassInfo parent) {
|
||||
if (inner.isInner()) {
|
||||
ClassInfo p = inner.getParentClass();
|
||||
@@ -423,19 +441,14 @@ public class ClassGen {
|
||||
}
|
||||
|
||||
private void insertSourceFileInfo(CodeWriter code, AttrNode node) {
|
||||
IAttribute sourceFileAttr = node.getAttributes().get(AttributeType.SOURCE_FILE);
|
||||
SourceFileAttr sourceFileAttr = (SourceFileAttr) node.getAttributes().get(AttributeType.SOURCE_FILE);
|
||||
if (sourceFileAttr != null) {
|
||||
code.startLine("// compiled from: ");
|
||||
code.add(((SourceFileAttr) sourceFileAttr).getFileName());
|
||||
code.startLine("// compiled from: ").add(sourceFileAttr.getFileName());
|
||||
}
|
||||
}
|
||||
|
||||
public Set<ClassInfo> getImports() {
|
||||
return imports;
|
||||
}
|
||||
|
||||
public ClassGen getParentGen() {
|
||||
return parentGen;
|
||||
return parentGen == null ? this : parentGen;
|
||||
}
|
||||
|
||||
public AnnotationGen getAnnotationGen() {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package jadx.core.codegen;
|
||||
|
||||
import jadx.api.CodePosition;
|
||||
import jadx.core.dex.attributes.LineAttrNode;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
@@ -7,6 +8,7 @@ import java.io.File;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
@@ -17,14 +19,24 @@ public class CodeWriter {
|
||||
private static final int MAX_FILENAME_LENGTH = 128;
|
||||
|
||||
public static final String NL = System.getProperty("line.separator");
|
||||
private static final String INDENT = "\t";
|
||||
public static final String INDENT = " ";
|
||||
|
||||
private static final String[] INDENT_CACHE = {
|
||||
"",
|
||||
INDENT,
|
||||
INDENT + INDENT,
|
||||
INDENT + INDENT + INDENT,
|
||||
INDENT + INDENT + INDENT + INDENT,
|
||||
INDENT + INDENT + INDENT + INDENT + INDENT,
|
||||
};
|
||||
|
||||
private final StringBuilder buf = new StringBuilder();
|
||||
private String indentStr;
|
||||
private int indent;
|
||||
|
||||
private int line = 1;
|
||||
private Map<Object, Integer> annotations = Collections.emptyMap();
|
||||
private int offset = 0;
|
||||
private Map<CodePosition, Object> annotations = Collections.emptyMap();
|
||||
|
||||
public CodeWriter() {
|
||||
this.indent = 0;
|
||||
@@ -38,50 +50,50 @@ public class CodeWriter {
|
||||
|
||||
public CodeWriter startLine() {
|
||||
addLine();
|
||||
buf.append(indentStr);
|
||||
addLineIndent();
|
||||
return this;
|
||||
}
|
||||
|
||||
public CodeWriter startLine(char c) {
|
||||
addLine();
|
||||
buf.append(indentStr);
|
||||
buf.append(c);
|
||||
addLineIndent();
|
||||
add(c);
|
||||
return this;
|
||||
}
|
||||
|
||||
public CodeWriter startLine(String str) {
|
||||
addLine();
|
||||
buf.append(indentStr);
|
||||
buf.append(str);
|
||||
addLineIndent();
|
||||
add(str);
|
||||
return this;
|
||||
}
|
||||
|
||||
public CodeWriter startLine(int ind, String str) {
|
||||
addLine();
|
||||
buf.append(indentStr);
|
||||
for (int i = 0; i < ind; i++)
|
||||
buf.append(INDENT);
|
||||
buf.append(str);
|
||||
public CodeWriter add(Object obj) {
|
||||
add(obj.toString());
|
||||
return this;
|
||||
}
|
||||
|
||||
public CodeWriter add(String str) {
|
||||
buf.append(str);
|
||||
offset += str.length();
|
||||
return this;
|
||||
}
|
||||
|
||||
public CodeWriter add(char c) {
|
||||
buf.append(c);
|
||||
offset++;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CodeWriter add(CodeWriter code) {
|
||||
CodeWriter add(CodeWriter code) {
|
||||
line--;
|
||||
for (Map.Entry<Object, Integer> entry : code.annotations.entrySet()) {
|
||||
attachAnnotation(entry.getKey(), line + entry.getValue());
|
||||
for (Map.Entry<CodePosition, Object> entry : code.annotations.entrySet()) {
|
||||
CodePosition pos = entry.getKey();
|
||||
attachAnnotation(entry.getValue(), new CodePosition(line + pos.getLine(), pos.getOffset()));
|
||||
}
|
||||
line += code.line;
|
||||
buf.append(code.toString());
|
||||
offset = code.offset;
|
||||
buf.append(code);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -93,40 +105,23 @@ public class CodeWriter {
|
||||
private void addLine() {
|
||||
buf.append(NL);
|
||||
line++;
|
||||
offset = 0;
|
||||
}
|
||||
|
||||
public int getLine() {
|
||||
return line;
|
||||
}
|
||||
|
||||
public Object attachAnnotation(Object obj) {
|
||||
return attachAnnotation(obj, line);
|
||||
}
|
||||
|
||||
public Object attachAnnotation(Object obj, int line) {
|
||||
if (annotations.isEmpty()) {
|
||||
annotations = new HashMap<Object, Integer>();
|
||||
}
|
||||
return annotations.put(obj, line);
|
||||
}
|
||||
|
||||
public CodeWriter indent() {
|
||||
private CodeWriter addLineIndent() {
|
||||
buf.append(indentStr);
|
||||
offset += indentStr.length();
|
||||
return this;
|
||||
}
|
||||
|
||||
private static final String[] INDENT_CACHE = new String[]{
|
||||
"",
|
||||
INDENT,
|
||||
INDENT + INDENT,
|
||||
INDENT + INDENT + INDENT,
|
||||
INDENT + INDENT + INDENT + INDENT,
|
||||
INDENT + INDENT + INDENT + INDENT + INDENT,
|
||||
};
|
||||
public CodeWriter addIndent() {
|
||||
add(INDENT);
|
||||
return this;
|
||||
}
|
||||
|
||||
private void updateIndent() {
|
||||
int curIndent = indent;
|
||||
if (curIndent < 6) {
|
||||
if (curIndent < INDENT_CACHE.length) {
|
||||
this.indentStr = INDENT_CACHE[curIndent];
|
||||
} else {
|
||||
StringBuilder s = new StringBuilder(curIndent * INDENT.length());
|
||||
@@ -137,10 +132,6 @@ public class CodeWriter {
|
||||
}
|
||||
}
|
||||
|
||||
public int getIndent() {
|
||||
return indent;
|
||||
}
|
||||
|
||||
public void incIndent() {
|
||||
incIndent(1);
|
||||
}
|
||||
@@ -163,16 +154,53 @@ public class CodeWriter {
|
||||
updateIndent();
|
||||
}
|
||||
|
||||
public int getLine() {
|
||||
return line;
|
||||
}
|
||||
|
||||
private static class DefinitionWrapper {
|
||||
private final LineAttrNode node;
|
||||
|
||||
private DefinitionWrapper(LineAttrNode node) {
|
||||
this.node = node;
|
||||
}
|
||||
|
||||
public LineAttrNode getNode() {
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
public Object attachDefinition(LineAttrNode obj) {
|
||||
return attachAnnotation(new DefinitionWrapper(obj), new CodePosition(line, offset));
|
||||
}
|
||||
|
||||
public Object attachAnnotation(Object obj) {
|
||||
return attachAnnotation(obj, new CodePosition(line, offset + 1));
|
||||
}
|
||||
|
||||
private Object attachAnnotation(Object obj, CodePosition pos) {
|
||||
if (annotations.isEmpty()) {
|
||||
annotations = new HashMap<CodePosition, Object>();
|
||||
}
|
||||
return annotations.put(pos, obj);
|
||||
}
|
||||
|
||||
public Map<CodePosition, Object> getAnnotations() {
|
||||
return annotations;
|
||||
}
|
||||
|
||||
public void finish() {
|
||||
buf.trimToSize();
|
||||
for (Map.Entry<Object, Integer> entry : annotations.entrySet()) {
|
||||
Object v = entry.getKey();
|
||||
if (v instanceof LineAttrNode) {
|
||||
LineAttrNode l = (LineAttrNode) v;
|
||||
l.setDecompiledLine(entry.getValue());
|
||||
Iterator<Map.Entry<CodePosition, Object>> it = annotations.entrySet().iterator();
|
||||
while (it.hasNext()) {
|
||||
Map.Entry<CodePosition, Object> entry = it.next();
|
||||
Object v = entry.getValue();
|
||||
if (v instanceof DefinitionWrapper) {
|
||||
LineAttrNode l = ((DefinitionWrapper) v).getNode();
|
||||
l.setDecompiledLine(entry.getKey().getLine());
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
annotations.clear();
|
||||
}
|
||||
|
||||
private static String removeFirstEmptyLine(String str) {
|
||||
@@ -183,6 +211,14 @@ public class CodeWriter {
|
||||
}
|
||||
}
|
||||
|
||||
public int length() {
|
||||
return buf.length();
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return buf.length() == 0;
|
||||
}
|
||||
|
||||
public boolean notEmpty() {
|
||||
return buf.length() != 0;
|
||||
}
|
||||
@@ -205,10 +241,11 @@ public class CodeWriter {
|
||||
if (name.length() > MAX_FILENAME_LENGTH) {
|
||||
int dotIndex = name.indexOf('.');
|
||||
int cutAt = MAX_FILENAME_LENGTH - name.length() + dotIndex - 1;
|
||||
if (cutAt <= 0)
|
||||
if (cutAt <= 0) {
|
||||
name = name.substring(0, MAX_FILENAME_LENGTH - 1);
|
||||
else
|
||||
} else {
|
||||
name = name.substring(0, cutAt) + name.substring(dotIndex);
|
||||
}
|
||||
file = new File(file.getParentFile(), name);
|
||||
}
|
||||
|
||||
@@ -222,9 +259,26 @@ public class CodeWriter {
|
||||
} catch (Exception e) {
|
||||
LOG.error("Save file error", e);
|
||||
} finally {
|
||||
if (out != null)
|
||||
if (out != null) {
|
||||
out.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return buf.toString().hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (!(o instanceof CodeWriter)) {
|
||||
return false;
|
||||
}
|
||||
CodeWriter that = (CodeWriter) o;
|
||||
return buf.toString().equals(that.buf.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
package jadx.core.codegen;
|
||||
|
||||
import jadx.core.dex.instructions.ArithNode;
|
||||
import jadx.core.dex.instructions.IfOp;
|
||||
import jadx.core.dex.instructions.InsnType;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.InsnWrapArg;
|
||||
import jadx.core.dex.instructions.args.LiteralArg;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.regions.Compare;
|
||||
import jadx.core.dex.regions.IfCondition;
|
||||
import jadx.core.utils.ErrorsCounter;
|
||||
import jadx.core.utils.exceptions.CodegenException;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class ConditionGen extends InsnGen {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ConditionGen.class);
|
||||
|
||||
public ConditionGen(InsnGen insnGen) {
|
||||
super(insnGen.mgen, insnGen.fallback);
|
||||
}
|
||||
|
||||
void add(CodeWriter code, IfCondition condition) throws CodegenException {
|
||||
switch (condition.getMode()) {
|
||||
case COMPARE:
|
||||
addCompare(code, condition.getCompare());
|
||||
break;
|
||||
|
||||
case NOT:
|
||||
addNot(code, condition);
|
||||
break;
|
||||
|
||||
case AND:
|
||||
case OR:
|
||||
addAndOr(code, condition);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new JadxRuntimeException("Unknown condition mode: " + condition);
|
||||
}
|
||||
}
|
||||
|
||||
void wrap(CodeWriter code, IfCondition cond) throws CodegenException {
|
||||
boolean wrap = isWrapNeeded(cond);
|
||||
if (wrap) {
|
||||
code.add('(');
|
||||
}
|
||||
add(code, cond);
|
||||
if (wrap) {
|
||||
code.add(')');
|
||||
}
|
||||
}
|
||||
|
||||
private void addCompare(CodeWriter code, Compare compare) throws CodegenException {
|
||||
IfOp op = compare.getOp();
|
||||
InsnArg firstArg = compare.getA();
|
||||
InsnArg secondArg = compare.getB();
|
||||
if (firstArg.getType().equals(ArgType.BOOLEAN)
|
||||
&& secondArg.isLiteral()
|
||||
&& secondArg.getType().equals(ArgType.BOOLEAN)) {
|
||||
LiteralArg lit = (LiteralArg) secondArg;
|
||||
if (lit.getLiteral() == 0) {
|
||||
op = op.invert();
|
||||
}
|
||||
if (op == IfOp.EQ) {
|
||||
// == true
|
||||
addArg(code, firstArg, false);
|
||||
return;
|
||||
} else if (op == IfOp.NE) {
|
||||
// != true
|
||||
code.add('!');
|
||||
boolean wrap = isWrapNeeded(firstArg);
|
||||
if (wrap) {
|
||||
code.add('(');
|
||||
}
|
||||
addArg(code, firstArg, false);
|
||||
if (wrap) {
|
||||
code.add(')');
|
||||
}
|
||||
return;
|
||||
}
|
||||
LOG.warn(ErrorsCounter.formatErrorMsg(mth, "Unsupported boolean condition " + op.getSymbol()));
|
||||
}
|
||||
|
||||
addArg(code, firstArg, isWrapNeeded(firstArg));
|
||||
code.add(' ').add(op.getSymbol()).add(' ');
|
||||
addArg(code, secondArg, isWrapNeeded(secondArg));
|
||||
}
|
||||
|
||||
private void addNot(CodeWriter code, IfCondition condition) throws CodegenException {
|
||||
code.add('!');
|
||||
wrap(code, condition.getArgs().get(0));
|
||||
}
|
||||
|
||||
private void addAndOr(CodeWriter code, IfCondition condition) throws CodegenException {
|
||||
String mode = condition.getMode() == IfCondition.Mode.AND ? " && " : " || ";
|
||||
Iterator<IfCondition> it = condition.getArgs().iterator();
|
||||
while (it.hasNext()) {
|
||||
wrap(code, it.next());
|
||||
if (it.hasNext()) {
|
||||
code.add(mode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isWrapNeeded(IfCondition condition) {
|
||||
return !condition.isCompare();
|
||||
}
|
||||
|
||||
private static boolean isWrapNeeded(InsnArg arg) {
|
||||
if (!arg.isInsnWrap()) {
|
||||
return false;
|
||||
}
|
||||
InsnNode insn = ((InsnWrapArg) arg).getWrapInsn();
|
||||
if (insn.getType() == InsnType.ARITH) {
|
||||
switch (((ArithNode) insn).getOp()) {
|
||||
case ADD:
|
||||
case SUB:
|
||||
case MUL:
|
||||
case DIV:
|
||||
case REM:
|
||||
return false;
|
||||
}
|
||||
} else if (insn.getType() == InsnType.INVOKE) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package jadx.core.codegen;
|
||||
|
||||
import jadx.core.dex.attributes.AttributeFlag;
|
||||
import jadx.core.dex.attributes.AttributeType;
|
||||
import jadx.core.dex.attributes.FieldReplaceAttr;
|
||||
import jadx.core.dex.attributes.IAttribute;
|
||||
import jadx.core.dex.attributes.MethodInlineAttr;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
@@ -15,6 +16,7 @@ import jadx.core.dex.instructions.FillArrayNode;
|
||||
import jadx.core.dex.instructions.GotoNode;
|
||||
import jadx.core.dex.instructions.IfNode;
|
||||
import jadx.core.dex.instructions.IndexInsnNode;
|
||||
import jadx.core.dex.instructions.InsnType;
|
||||
import jadx.core.dex.instructions.InvokeNode;
|
||||
import jadx.core.dex.instructions.InvokeType;
|
||||
import jadx.core.dex.instructions.SwitchNode;
|
||||
@@ -26,6 +28,7 @@ import jadx.core.dex.instructions.args.LiteralArg;
|
||||
import jadx.core.dex.instructions.args.NamedArg;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.instructions.mods.ConstructorInsn;
|
||||
import jadx.core.dex.instructions.mods.TernaryInsn;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
@@ -36,6 +39,7 @@ import jadx.core.utils.InsnUtils;
|
||||
import jadx.core.utils.RegionUtils;
|
||||
import jadx.core.utils.StringUtils;
|
||||
import jadx.core.utils.exceptions.CodegenException;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumSet;
|
||||
@@ -43,7 +47,6 @@ import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -54,21 +57,16 @@ public class InsnGen {
|
||||
protected final MethodGen mgen;
|
||||
protected final MethodNode mth;
|
||||
protected final RootNode root;
|
||||
private final boolean fallback;
|
||||
|
||||
private static enum IGState {
|
||||
SKIP,
|
||||
|
||||
NO_SEMICOLON,
|
||||
NO_RESULT,
|
||||
protected final boolean fallback;
|
||||
|
||||
private static enum Flags {
|
||||
BODY_ONLY,
|
||||
BODY_ONLY_NOWRAP,
|
||||
}
|
||||
|
||||
public InsnGen(MethodGen mgen, MethodNode mth, boolean fallback) {
|
||||
public InsnGen(MethodGen mgen, boolean fallback) {
|
||||
this.mgen = mgen;
|
||||
this.mth = mth;
|
||||
this.mth = mgen.getMethodNode();
|
||||
this.root = mth.dex().root();
|
||||
this.fallback = fallback;
|
||||
}
|
||||
@@ -77,105 +75,92 @@ public class InsnGen {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
public String arg(InsnNode insn, int arg) throws CodegenException {
|
||||
return arg(insn.getArg(arg));
|
||||
public void addArgDot(CodeWriter code, InsnArg arg) throws CodegenException {
|
||||
int len = code.length();
|
||||
addArg(code, arg, true);
|
||||
if (len != code.length()) {
|
||||
code.add('.');
|
||||
}
|
||||
}
|
||||
|
||||
public String arg(InsnArg arg) throws CodegenException {
|
||||
return arg(arg, true);
|
||||
public void addArg(CodeWriter code, InsnArg arg) throws CodegenException {
|
||||
addArg(code, arg, true);
|
||||
}
|
||||
|
||||
public String arg(InsnArg arg, boolean wrap) throws CodegenException {
|
||||
public void addArg(CodeWriter code, InsnArg arg, boolean wrap) throws CodegenException {
|
||||
if (arg.isRegister()) {
|
||||
return mgen.makeArgName((RegisterArg) arg);
|
||||
code.add(mgen.makeArgName((RegisterArg) arg));
|
||||
} else if (arg.isLiteral()) {
|
||||
return lit((LiteralArg) arg);
|
||||
code.add(lit((LiteralArg) arg));
|
||||
} else if (arg.isInsnWrap()) {
|
||||
CodeWriter code = new CodeWriter();
|
||||
IGState flag = wrap ? IGState.BODY_ONLY : IGState.BODY_ONLY_NOWRAP;
|
||||
Flags flag = wrap ? Flags.BODY_ONLY : Flags.BODY_ONLY_NOWRAP;
|
||||
makeInsn(((InsnWrapArg) arg).getWrapInsn(), code, flag);
|
||||
return code.toString();
|
||||
} else if (arg.isNamed()) {
|
||||
return ((NamedArg) arg).getName();
|
||||
code.add(((NamedArg) arg).getName());
|
||||
} else if (arg.isField()) {
|
||||
FieldArg f = (FieldArg) arg;
|
||||
if (f.isStatic()) {
|
||||
return sfield(f.getField());
|
||||
code.add(staticField(f.getField()));
|
||||
} else {
|
||||
RegisterArg regArg = new RegisterArg(f.getRegNum());
|
||||
regArg.replaceTypedVar(f);
|
||||
return ifield(f.getField(), regArg);
|
||||
instanceField(code, f.getField(), regArg);
|
||||
}
|
||||
} else {
|
||||
throw new CodegenException("Unknown arg type " + arg);
|
||||
}
|
||||
}
|
||||
|
||||
public String assignVar(InsnNode insn) throws CodegenException {
|
||||
public void assignVar(CodeWriter code, InsnNode insn) throws CodegenException {
|
||||
RegisterArg arg = insn.getResult();
|
||||
if (insn.getAttributes().contains(AttributeType.DECLARE_VARIABLE)) {
|
||||
return declareVar(arg);
|
||||
if (insn.getAttributes().contains(AttributeFlag.DECLARE_VAR)) {
|
||||
declareVar(code, arg);
|
||||
} else {
|
||||
return arg(arg);
|
||||
addArg(code, arg, false);
|
||||
}
|
||||
}
|
||||
|
||||
public String declareVar(RegisterArg arg) {
|
||||
return useType(arg.getType()) + " " + mgen.assignArg(arg);
|
||||
public void declareVar(CodeWriter code, RegisterArg arg) {
|
||||
code.add(useType(arg.getType()));
|
||||
code.add(' ');
|
||||
code.add(mgen.assignArg(arg));
|
||||
}
|
||||
|
||||
private String lit(LiteralArg arg) {
|
||||
private static String lit(LiteralArg arg) {
|
||||
return TypeGen.literalToString(arg.getLiteral(), arg.getType());
|
||||
}
|
||||
|
||||
private String ifield(FieldInfo field, InsnArg arg) throws CodegenException {
|
||||
private void instanceField(CodeWriter code, FieldInfo field, InsnArg arg) throws CodegenException {
|
||||
FieldNode fieldNode = mth.getParentClass().searchField(field);
|
||||
if(fieldNode != null && fieldNode.getAttributes().contains(AttributeFlag.DONT_GENERATE)) {
|
||||
return "";
|
||||
}
|
||||
String name = field.getName();
|
||||
// TODO: add jadx argument "add this"
|
||||
// FIXME: check variable names in scope
|
||||
if (false && arg.isThis()) {
|
||||
boolean useShort = true;
|
||||
List<RegisterArg> args = mth.getArguments(false);
|
||||
for (RegisterArg param : args) {
|
||||
String paramName = param.getTypedVar().getName();
|
||||
if (paramName != null && paramName.equals(name)) {
|
||||
useShort = false;
|
||||
if (fieldNode != null) {
|
||||
FieldReplaceAttr replace = (FieldReplaceAttr) fieldNode.getAttributes().get(AttributeType.FIELD_REPLACE);
|
||||
if (replace != null) {
|
||||
FieldInfo info = replace.getFieldInfo();
|
||||
if (replace.isOuterClass()) {
|
||||
code.add(useClass(info.getDeclClass())).add(".this");
|
||||
}
|
||||
}
|
||||
if (useShort) {
|
||||
return name;
|
||||
return;
|
||||
}
|
||||
}
|
||||
String argStr = arg(arg);
|
||||
return argStr.isEmpty() ? name : argStr + "." + name;
|
||||
addArgDot(code, arg);
|
||||
code.add(field.getName());
|
||||
}
|
||||
|
||||
protected String sfield(FieldInfo field) {
|
||||
String thisClass = mth.getParentClass().getFullName();
|
||||
public static String makeStaticFieldAccess(FieldInfo field, ClassGen clsGen) {
|
||||
ClassInfo declClass = field.getDeclClass();
|
||||
if (thisClass.startsWith(declClass.getFullName())) {
|
||||
if (clsGen.getClassNode().getFullName().startsWith(declClass.getFullName())) {
|
||||
return field.getName();
|
||||
}
|
||||
// Android specific resources class handler
|
||||
ClassInfo parentClass = declClass.getParentClass();
|
||||
if (parentClass != null && parentClass.getShortName().equals("R")) {
|
||||
return useClass(parentClass) + "." + declClass.getShortName() + "." + field.getName();
|
||||
return clsGen.useClass(parentClass) + "." + declClass.getShortName() + "." + field.getName();
|
||||
}
|
||||
return useClass(declClass) + '.' + field.getName();
|
||||
return clsGen.useClass(declClass) + '.' + field.getName();
|
||||
}
|
||||
|
||||
private void fieldPut(IndexInsnNode insn) {
|
||||
FieldInfo field = (FieldInfo) insn.getIndex();
|
||||
String thisClass = mth.getParentClass().getFullName();
|
||||
if (field.getDeclClass().getFullName().equals(thisClass)) {
|
||||
// if we generate this field - don't init if its final and used
|
||||
FieldNode fn = mth.getParentClass().searchField(field);
|
||||
if (fn != null && fn.getAccessFlags().isFinal())
|
||||
fn.getAttributes().remove(AttributeType.FIELD_VALUE);
|
||||
}
|
||||
protected String staticField(FieldInfo field) {
|
||||
return makeStaticFieldAccess(field, mgen.getClassGen());
|
||||
}
|
||||
|
||||
public String useClass(ClassInfo cls) {
|
||||
@@ -190,31 +175,26 @@ public class InsnGen {
|
||||
return makeInsn(insn, code, null);
|
||||
}
|
||||
|
||||
private boolean makeInsn(InsnNode insn, CodeWriter code, IGState flag) throws CodegenException {
|
||||
private boolean makeInsn(InsnNode insn, CodeWriter code, Flags flag) throws CodegenException {
|
||||
try {
|
||||
EnumSet<IGState> state = EnumSet.noneOf(IGState.class);
|
||||
if (flag == IGState.BODY_ONLY || flag == IGState.BODY_ONLY_NOWRAP) {
|
||||
if (insn.getType() == InsnType.NOP) {
|
||||
return false;
|
||||
}
|
||||
EnumSet<Flags> state = EnumSet.noneOf(Flags.class);
|
||||
if (flag == Flags.BODY_ONLY || flag == Flags.BODY_ONLY_NOWRAP) {
|
||||
state.add(flag);
|
||||
makeInsnBody(code, insn, state);
|
||||
} else {
|
||||
CodeWriter body = new CodeWriter(code.getIndent());
|
||||
makeInsnBody(body, insn, state);
|
||||
if (state.contains(IGState.SKIP)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
code.startLine();
|
||||
if (insn.getSourceLine() != 0) {
|
||||
code.attachAnnotation(insn.getSourceLine());
|
||||
}
|
||||
if (insn.getResult() != null && !state.contains(IGState.NO_RESULT)) {
|
||||
code.add(assignVar(insn)).add(" = ");
|
||||
}
|
||||
code.add(body);
|
||||
|
||||
if (!state.contains(IGState.NO_SEMICOLON)) {
|
||||
code.add(';');
|
||||
if (insn.getResult() != null && insn.getType() != InsnType.ARITH_ONEARG) {
|
||||
assignVar(code, insn);
|
||||
code.add(" = ");
|
||||
}
|
||||
makeInsnBody(code, insn, state);
|
||||
code.add(';');
|
||||
}
|
||||
} catch (Throwable th) {
|
||||
throw new CodegenException(mth, "Error generate insn: " + insn, th);
|
||||
@@ -222,7 +202,7 @@ public class InsnGen {
|
||||
return true;
|
||||
}
|
||||
|
||||
private void makeInsnBody(CodeWriter code, InsnNode insn, EnumSet<IGState> state) throws CodegenException {
|
||||
private void makeInsnBody(CodeWriter code, InsnNode insn, EnumSet<Flags> state) throws CodegenException {
|
||||
switch (insn.getType()) {
|
||||
case CONST_STR:
|
||||
String str = ((ConstStringNode) insn).getString();
|
||||
@@ -240,40 +220,53 @@ public class InsnGen {
|
||||
break;
|
||||
|
||||
case MOVE:
|
||||
code.add(arg(insn.getArg(0), false));
|
||||
addArg(code, insn.getArg(0), false);
|
||||
break;
|
||||
|
||||
case CHECK_CAST:
|
||||
case CAST: {
|
||||
boolean wrap = state.contains(IGState.BODY_ONLY);
|
||||
if (wrap)
|
||||
code.add("(");
|
||||
code.add("(");
|
||||
code.add(useType(((ArgType) ((IndexInsnNode) insn).getIndex())));
|
||||
boolean wrap = state.contains(Flags.BODY_ONLY);
|
||||
if (wrap) {
|
||||
code.add('(');
|
||||
}
|
||||
code.add('(');
|
||||
code.add(useType((ArgType) ((IndexInsnNode) insn).getIndex()));
|
||||
code.add(") ");
|
||||
code.add(arg(insn.getArg(0)));
|
||||
if (wrap)
|
||||
code.add(")");
|
||||
addArg(code, insn.getArg(0), true);
|
||||
if (wrap) {
|
||||
code.add(')');
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case ARITH:
|
||||
makeArith((ArithNode) insn, code, state);
|
||||
break;
|
||||
|
||||
case NEG:
|
||||
String base = "-" + arg(insn.getArg(0));
|
||||
if (state.contains(IGState.BODY_ONLY)) {
|
||||
code.add('(').add(base).add(')');
|
||||
} else {
|
||||
code.add(base);
|
||||
}
|
||||
case ARITH_ONEARG:
|
||||
makeArithOneArg((ArithNode) insn, code, state);
|
||||
break;
|
||||
|
||||
case NEG: {
|
||||
boolean wrap = state.contains(Flags.BODY_ONLY);
|
||||
if (wrap) {
|
||||
code.add('(');
|
||||
}
|
||||
code.add('-');
|
||||
addArg(code, insn.getArg(0));
|
||||
if (wrap) {
|
||||
code.add(')');
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case RETURN:
|
||||
if (insn.getArgsCount() != 0)
|
||||
code.add("return ").add(arg(insn.getArg(0), false));
|
||||
else
|
||||
if (insn.getArgsCount() != 0) {
|
||||
code.add("return ");
|
||||
addArg(code, insn.getArg(0), false);
|
||||
} else {
|
||||
code.add("return");
|
||||
}
|
||||
break;
|
||||
|
||||
case BREAK:
|
||||
@@ -285,20 +278,29 @@ public class InsnGen {
|
||||
break;
|
||||
|
||||
case THROW:
|
||||
code.add("throw ").add(arg(insn.getArg(0), true));
|
||||
code.add("throw ");
|
||||
addArg(code, insn.getArg(0), true);
|
||||
break;
|
||||
|
||||
case CMP_L:
|
||||
case CMP_G:
|
||||
code.add(String.format("(%1$s > %2$s ? 1 : (%1$s == %2$s ? 0 : -1))", arg(insn, 0), arg(insn, 1)));
|
||||
code.add('(');
|
||||
addArg(code, insn.getArg(0));
|
||||
code.add(" > ");
|
||||
addArg(code, insn.getArg(1));
|
||||
code.add(" ? 1 : (");
|
||||
addArg(code, insn.getArg(0));
|
||||
code.add(" == ");
|
||||
addArg(code, insn.getArg(1));
|
||||
code.add("? 0 : -1))");
|
||||
break;
|
||||
|
||||
case INSTANCE_OF: {
|
||||
boolean wrap = state.contains(IGState.BODY_ONLY);
|
||||
boolean wrap = state.contains(Flags.BODY_ONLY);
|
||||
if (wrap) {
|
||||
code.add('(');
|
||||
}
|
||||
code.add(arg(insn, 0));
|
||||
addArg(code, insn.getArg(0));
|
||||
code.add(" instanceof ");
|
||||
code.add(useType((ArgType) ((IndexInsnNode) insn).getIndex()));
|
||||
if (wrap) {
|
||||
@@ -316,15 +318,20 @@ public class InsnGen {
|
||||
|
||||
case NEW_ARRAY: {
|
||||
ArgType arrayType = insn.getResult().getType();
|
||||
code.add("new ").add(useType(arrayType.getArrayRootElement()));
|
||||
code.add('[');
|
||||
addArg(code, insn.getArg(0));
|
||||
code.add(']');
|
||||
int dim = arrayType.getArrayDimension();
|
||||
code.add("new ").add(useType(arrayType.getArrayRootElement())).add('[').add(arg(insn, 0)).add(']');
|
||||
for (int i = 0; i < dim - 1; i++)
|
||||
for (int i = 0; i < dim - 1; i++) {
|
||||
code.add("[]");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case ARRAY_LENGTH:
|
||||
code.add(arg(insn, 0)).add(".length");
|
||||
addArg(code, insn.getArg(0));
|
||||
code.add(".length");
|
||||
break;
|
||||
|
||||
case FILL_ARRAY:
|
||||
@@ -336,62 +343,71 @@ public class InsnGen {
|
||||
break;
|
||||
|
||||
case AGET:
|
||||
code.add(arg(insn.getArg(0))).add('[').add(arg(insn.getArg(1), false)).add(']');
|
||||
addArg(code, insn.getArg(0));
|
||||
code.add('[');
|
||||
addArg(code, insn.getArg(1), false);
|
||||
code.add(']');
|
||||
break;
|
||||
|
||||
case APUT:
|
||||
code.add(arg(insn, 0)).add('[').add(arg(insn.getArg(1), false)).add("] = ").add(arg(insn.getArg(2), false));
|
||||
addArg(code, insn.getArg(0));
|
||||
code.add('[');
|
||||
addArg(code, insn.getArg(1), false);
|
||||
code.add("] = ");
|
||||
addArg(code, insn.getArg(2), false);
|
||||
break;
|
||||
|
||||
case IGET: {
|
||||
FieldInfo fieldInfo = (FieldInfo) ((IndexInsnNode) insn).getIndex();
|
||||
code.add(ifield(fieldInfo, insn.getArg(0)));
|
||||
instanceField(code, fieldInfo, insn.getArg(0));
|
||||
break;
|
||||
}
|
||||
case IPUT: {
|
||||
FieldInfo fieldInfo = (FieldInfo) ((IndexInsnNode) insn).getIndex();
|
||||
code.add(ifield(fieldInfo, insn.getArg(1))).add(" = ").add(arg(insn.getArg(0), false));
|
||||
instanceField(code, fieldInfo, insn.getArg(1));
|
||||
code.add(" = ");
|
||||
addArg(code, insn.getArg(0), false);
|
||||
break;
|
||||
}
|
||||
|
||||
case SGET:
|
||||
code.add(sfield((FieldInfo) ((IndexInsnNode) insn).getIndex()));
|
||||
code.add(staticField((FieldInfo) ((IndexInsnNode) insn).getIndex()));
|
||||
break;
|
||||
case SPUT:
|
||||
IndexInsnNode node = (IndexInsnNode) insn;
|
||||
fieldPut(node);
|
||||
code.add(sfield((FieldInfo) node.getIndex())).add(" = ").add(arg(node.getArg(0), false));
|
||||
FieldInfo field = (FieldInfo) ((IndexInsnNode) insn).getIndex();
|
||||
code.add(staticField(field)).add(" = ");
|
||||
addArg(code, insn.getArg(0), false);
|
||||
break;
|
||||
|
||||
case STR_CONCAT:
|
||||
StringBuilder sb = new StringBuilder();
|
||||
boolean wrap = state.contains(Flags.BODY_ONLY);
|
||||
if (wrap) {
|
||||
code.add('(');
|
||||
}
|
||||
for (Iterator<InsnArg> it = insn.getArguments().iterator(); it.hasNext(); ) {
|
||||
sb.append(arg(it.next()));
|
||||
addArg(code, it.next());
|
||||
if (it.hasNext()) {
|
||||
sb.append(" + ");
|
||||
code.add(" + ");
|
||||
}
|
||||
}
|
||||
// TODO: wrap in braces only if necessary
|
||||
if (state.contains(IGState.BODY_ONLY)) {
|
||||
code.add('(').add(sb.toString()).add(')');
|
||||
} else {
|
||||
code.add(sb.toString());
|
||||
if (wrap) {
|
||||
code.add(')');
|
||||
}
|
||||
break;
|
||||
|
||||
case MONITOR_ENTER:
|
||||
if (isFallback()) {
|
||||
code.add("monitor-enter(").add(arg(insn.getArg(0))).add(')');
|
||||
} else {
|
||||
state.add(IGState.SKIP);
|
||||
code.add("monitor-enter(");
|
||||
addArg(code, insn.getArg(0));
|
||||
code.add(')');
|
||||
}
|
||||
break;
|
||||
|
||||
case MONITOR_EXIT:
|
||||
if (isFallback()) {
|
||||
code.add("monitor-exit(").add(arg(insn, 0)).add(')');
|
||||
} else {
|
||||
state.add(IGState.SKIP);
|
||||
code.add("monitor-exit(");
|
||||
addArg(code, insn.getArg(0));
|
||||
code.add(')');
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -399,28 +415,28 @@ public class InsnGen {
|
||||
if (isFallback()) {
|
||||
code.add("move-exception");
|
||||
} else {
|
||||
code.add(arg(insn, 0));
|
||||
addArg(code, insn.getArg(0));
|
||||
}
|
||||
break;
|
||||
|
||||
case TERNARY:
|
||||
makeTernary((TernaryInsn) insn, code, state);
|
||||
break;
|
||||
|
||||
case ARGS:
|
||||
code.add(arg(insn, 0));
|
||||
break;
|
||||
|
||||
case NOP:
|
||||
state.add(IGState.SKIP);
|
||||
addArg(code, insn.getArg(0));
|
||||
break;
|
||||
|
||||
/* fallback mode instructions */
|
||||
case IF:
|
||||
assert isFallback() : "if insn in not fallback mode";
|
||||
IfNode ifInsn = (IfNode) insn;
|
||||
String cond = arg(insn.getArg(0)) + " " + ifInsn.getOp().getSymbol() + " "
|
||||
+ (ifInsn.isZeroCmp() ? "0" : arg(insn.getArg(1)));
|
||||
code.add("if (").add(cond).add(") goto ").add(MethodGen.getLabelName(ifInsn.getTarget()));
|
||||
code.add("if (");
|
||||
addArg(code, insn.getArg(0));
|
||||
code.add(' ');
|
||||
code.add(ifInsn.getOp().getSymbol()).add(' ');
|
||||
addArg(code, insn.getArg(1));
|
||||
code.add(") goto ").add(MethodGen.getLabelName(ifInsn.getTarget()));
|
||||
break;
|
||||
|
||||
case GOTO:
|
||||
@@ -431,16 +447,18 @@ public class InsnGen {
|
||||
case SWITCH:
|
||||
assert isFallback();
|
||||
SwitchNode sw = (SwitchNode) insn;
|
||||
code.add("switch(").add(arg(insn, 0)).add(") {");
|
||||
code.add("switch(");
|
||||
addArg(code, insn.getArg(0));
|
||||
code.add(") {");
|
||||
code.incIndent();
|
||||
for (int i = 0; i < sw.getCasesCount(); i++) {
|
||||
code.startLine("case " + sw.getKeys()[i]
|
||||
+ ": goto " + MethodGen.getLabelName(sw.getTargets()[i]) + ";");
|
||||
code.startLine("case ").add(sw.getKeys()[i]).add(": goto ");
|
||||
code.add(MethodGen.getLabelName(sw.getTargets()[i])).add(';');
|
||||
}
|
||||
code.startLine("default: goto " + MethodGen.getLabelName(sw.getDefaultCaseOffset()) + ";");
|
||||
code.startLine("default: goto ");
|
||||
code.add(MethodGen.getLabelName(sw.getDefaultCaseOffset())).add(';');
|
||||
code.decIndent();
|
||||
code.startLine('}');
|
||||
state.add(IGState.NO_SEMICOLON);
|
||||
break;
|
||||
|
||||
case NEW_INSTANCE:
|
||||
@@ -459,9 +477,10 @@ public class InsnGen {
|
||||
code.add("new ").add(useType(insn.getResult().getType()));
|
||||
code.add('{');
|
||||
for (int i = 0; i < c; i++) {
|
||||
code.add(arg(insn, i));
|
||||
if (i + 1 < c)
|
||||
addArg(code, insn.getArg(i));
|
||||
if (i + 1 < c) {
|
||||
code.add(", ");
|
||||
}
|
||||
}
|
||||
code.add('}');
|
||||
}
|
||||
@@ -473,7 +492,8 @@ public class InsnGen {
|
||||
if (!elType.equals(insnElementType) && !insnArrayType.equals(ArgType.OBJECT)) {
|
||||
ErrorsCounter.methodError(mth,
|
||||
"Incorrect type for fill-array insn " + InsnUtils.formatOffset(insn.getOffset())
|
||||
+ ", element type: " + elType + ", insn element type: " + insnElementType);
|
||||
+ ", element type: " + elType + ", insn element type: " + insnElementType
|
||||
);
|
||||
if (!elType.isTypeKnown()) {
|
||||
elType = insnElementType.isTypeKnown() ? insnElementType : elType.selectFirst();
|
||||
}
|
||||
@@ -522,7 +542,7 @@ public class InsnGen {
|
||||
code.add("new ").add(useType(elType)).add("[]{").add(str.toString()).add('}');
|
||||
}
|
||||
|
||||
private void makeConstructor(ConstructorInsn insn, CodeWriter code, EnumSet<IGState> state)
|
||||
private void makeConstructor(ConstructorInsn insn, CodeWriter code, EnumSet<Flags> state)
|
||||
throws CodegenException {
|
||||
ClassNode cls = mth.dex().resolveClass(insn.getClassType());
|
||||
if (cls != null && cls.isAnonymous()) {
|
||||
@@ -533,6 +553,7 @@ public class InsnGen {
|
||||
} else {
|
||||
parent = cls.getSuperClass();
|
||||
}
|
||||
cls.getAttributes().add(AttributeFlag.DONT_GENERATE);
|
||||
MethodNode defCtr = cls.getDefaultConstructor();
|
||||
if (RegionUtils.notEmpty(defCtr.getRegion())) {
|
||||
defCtr.getAttributes().add(AttributeFlag.ANONYMOUS_CONSTRUCTOR);
|
||||
@@ -540,15 +561,11 @@ public class InsnGen {
|
||||
defCtr.getAttributes().add(AttributeFlag.DONT_GENERATE);
|
||||
}
|
||||
code.add("new ").add(parent == null ? "Object" : useClass(parent)).add("() ");
|
||||
code.incIndent(2);
|
||||
new ClassGen(cls, mgen.getClassGen().getParentGen(), fallback).makeClassBody(code);
|
||||
code.decIndent(2);
|
||||
new ClassGen(cls, mgen.getClassGen().getParentGen(), fallback).addClassBody(code);
|
||||
return;
|
||||
}
|
||||
if (insn.isSelf()) {
|
||||
// skip
|
||||
state.add(IGState.SKIP);
|
||||
return;
|
||||
throw new JadxRuntimeException("Constructor 'self' invoke must be removed!");
|
||||
}
|
||||
if (insn.isSuper()) {
|
||||
code.add("super");
|
||||
@@ -580,10 +597,7 @@ public class InsnGen {
|
||||
InsnArg arg = insn.getArg(0);
|
||||
// FIXME: add 'this' for equals methods in scope
|
||||
if (!arg.isThis()) {
|
||||
String argStr = arg(arg);
|
||||
if(!argStr.isEmpty()) {
|
||||
code.add(argStr).add('.');
|
||||
}
|
||||
addArgDot(code, arg);
|
||||
}
|
||||
k++;
|
||||
break;
|
||||
@@ -602,6 +616,9 @@ public class InsnGen {
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (callMthNode != null) {
|
||||
code.attachAnnotation(callMthNode);
|
||||
}
|
||||
code.add(callMth.getName());
|
||||
generateArguments(code, insn, k, callMthNode);
|
||||
}
|
||||
@@ -620,9 +637,10 @@ public class InsnGen {
|
||||
InsnArg arg = insn.getArg(i);
|
||||
ArgType origType = originalType.get(origPos);
|
||||
if (!arg.getType().equals(origType)) {
|
||||
code.add('(').add(useType(origType)).add(')').add(arg(arg, true));
|
||||
code.add('(').add(useType(origType)).add(')');
|
||||
addArg(code, arg, true);
|
||||
} else {
|
||||
code.add(arg(arg, false));
|
||||
addArg(code, arg, false);
|
||||
}
|
||||
if (i < argsCount - 1) {
|
||||
code.add(", ");
|
||||
@@ -633,10 +651,10 @@ public class InsnGen {
|
||||
} else {
|
||||
code.add('(');
|
||||
if (k < argsCount) {
|
||||
code.add(arg(insn.getArg(k), false));
|
||||
addArg(code, insn.getArg(k), false);
|
||||
for (int i = k + 1; i < argsCount; i++) {
|
||||
code.add(", ");
|
||||
code.add(arg(insn.getArg(i), false));
|
||||
addArg(code, insn.getArg(i), false);
|
||||
}
|
||||
}
|
||||
code.add(')');
|
||||
@@ -647,7 +665,7 @@ public class InsnGen {
|
||||
IAttribute mia = callMthNode.getAttributes().get(AttributeType.METHOD_INLINE);
|
||||
InsnNode inl = ((MethodInlineAttr) mia).getInsn();
|
||||
if (callMthNode.getMethodInfo().getArgumentsTypes().isEmpty()) {
|
||||
makeInsn(inl, code, IGState.BODY_ONLY);
|
||||
makeInsn(inl, code, Flags.BODY_ONLY);
|
||||
} else {
|
||||
// remap args
|
||||
InsnArg[] regs = new InsnArg[callMthNode.getRegsCount()];
|
||||
@@ -674,41 +692,69 @@ public class InsnGen {
|
||||
}
|
||||
}
|
||||
}
|
||||
makeInsn(inl, code, IGState.BODY_ONLY);
|
||||
makeInsn(inl, code, Flags.BODY_ONLY);
|
||||
// revert changes
|
||||
for (Entry<RegisterArg, InsnArg> e : toRevert.entrySet()) {
|
||||
for (Map.Entry<RegisterArg, InsnArg> e : toRevert.entrySet()) {
|
||||
inl.replaceArg(e.getValue(), e.getKey());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void makeArith(ArithNode insn, CodeWriter code, EnumSet<IGState> state) throws CodegenException {
|
||||
ArithOp op = insn.getOp();
|
||||
String v1 = arg(insn.getArg(0));
|
||||
String v2 = arg(insn.getArg(1));
|
||||
if (state.contains(IGState.BODY_ONLY)) {
|
||||
// wrap insn in brackets for save correct operation order
|
||||
code.add('(').add(v1).add(' ').add(op.getSymbol()).add(' ').add(v2).add(')');
|
||||
} else if (state.contains(IGState.BODY_ONLY_NOWRAP)) {
|
||||
code.add(v1).add(' ').add(op.getSymbol()).add(' ').add(v2);
|
||||
private void makeTernary(TernaryInsn insn, CodeWriter code, EnumSet<Flags> state) throws CodegenException {
|
||||
boolean wrap = state.contains(Flags.BODY_ONLY);
|
||||
if (wrap) {
|
||||
code.add('(');
|
||||
}
|
||||
InsnArg first = insn.getArg(0);
|
||||
InsnArg second = insn.getArg(1);
|
||||
ConditionGen condGen = new ConditionGen(this);
|
||||
if (first.equals(LiteralArg.TRUE) && second.equals(LiteralArg.FALSE)) {
|
||||
condGen.add(code, insn.getCondition());
|
||||
} else {
|
||||
String res = arg(insn.getResult());
|
||||
if (res.equals(v1) && insn.getResult().equals(insn.getArg(0))) {
|
||||
state.add(IGState.NO_RESULT);
|
||||
// "++" or "--"
|
||||
if (insn.getArg(1).isLiteral() && (op == ArithOp.ADD || op == ArithOp.SUB)) {
|
||||
LiteralArg lit = (LiteralArg) insn.getArg(1);
|
||||
if (lit.isInteger() && lit.getLiteral() == 1) {
|
||||
code.add(assignVar(insn)).add(op.getSymbol()).add(op.getSymbol());
|
||||
return;
|
||||
}
|
||||
}
|
||||
// +=, -= ...
|
||||
v2 = arg(insn.getArg(1), false);
|
||||
code.add(assignVar(insn)).add(' ').add(op.getSymbol()).add("= ").add(v2);
|
||||
} else {
|
||||
code.add(v1).add(' ').add(op.getSymbol()).add(' ').add(v2);
|
||||
}
|
||||
condGen.wrap(code, insn.getCondition());
|
||||
code.add(" ? ");
|
||||
addArg(code, first, false);
|
||||
code.add(" : ");
|
||||
addArg(code, second, false);
|
||||
}
|
||||
if (wrap) {
|
||||
code.add(')');
|
||||
}
|
||||
}
|
||||
|
||||
private void makeArith(ArithNode insn, CodeWriter code, EnumSet<Flags> state) throws CodegenException {
|
||||
// wrap insn in brackets for save correct operation order
|
||||
boolean wrap = state.contains(Flags.BODY_ONLY)
|
||||
&& !insn.getAttributes().contains(AttributeFlag.DONT_WRAP);
|
||||
if (wrap) {
|
||||
code.add('(');
|
||||
}
|
||||
addArg(code, insn.getArg(0));
|
||||
code.add(' ');
|
||||
code.add(insn.getOp().getSymbol());
|
||||
code.add(' ');
|
||||
addArg(code, insn.getArg(1));
|
||||
if (wrap) {
|
||||
code.add(')');
|
||||
}
|
||||
}
|
||||
|
||||
private void makeArithOneArg(ArithNode insn, CodeWriter code, EnumSet<Flags> state) throws CodegenException {
|
||||
ArithOp op = insn.getOp();
|
||||
InsnArg arg = insn.getArg(0);
|
||||
// "++" or "--"
|
||||
if (arg.isLiteral() && (op == ArithOp.ADD || op == ArithOp.SUB)) {
|
||||
LiteralArg lit = (LiteralArg) arg;
|
||||
if (lit.isInteger() && lit.getLiteral() == 1) {
|
||||
assignVar(code, insn);
|
||||
String opSymbol = op.getSymbol();
|
||||
code.add(opSymbol).add(opSymbol);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// +=, -= ...
|
||||
assignVar(code, insn);
|
||||
code.add(' ').add(op.getSymbol()).add("= ");
|
||||
addArg(code, arg, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,8 +12,9 @@ import jadx.core.dex.instructions.args.NamedArg;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.regions.Region;
|
||||
import jadx.core.dex.trycatch.CatchAttr;
|
||||
import jadx.core.dex.visitors.DepthTraverser;
|
||||
import jadx.core.dex.visitors.DepthTraversal;
|
||||
import jadx.core.dex.visitors.FallbackModeVisitor;
|
||||
import jadx.core.utils.ErrorsCounter;
|
||||
import jadx.core.utils.InsnUtils;
|
||||
@@ -57,16 +58,20 @@ public class MethodGen {
|
||||
return classGen;
|
||||
}
|
||||
|
||||
public MethodNode getMethodNode() {
|
||||
return mth;
|
||||
}
|
||||
|
||||
public boolean addDefinition(CodeWriter code) {
|
||||
if (mth.getMethodInfo().isClassInit()) {
|
||||
code.startLine("static");
|
||||
code.attachAnnotation(mth);
|
||||
code.attachDefinition(mth);
|
||||
return true;
|
||||
}
|
||||
if (mth.getAttributes().contains(AttributeFlag.ANONYMOUS_CONSTRUCTOR)) {
|
||||
// don't add method name and arguments
|
||||
code.startLine();
|
||||
code.attachAnnotation(mth);
|
||||
code.attachDefinition(mth);
|
||||
return false;
|
||||
}
|
||||
annotationGen.addForMethod(code, mth);
|
||||
@@ -83,7 +88,7 @@ public class MethodGen {
|
||||
}
|
||||
code.startLine(ai.makeString());
|
||||
|
||||
if (classGen.makeGenericMap(code, mth.getGenericMap())) {
|
||||
if (classGen.addGenericMap(code, mth.getGenericMap())) {
|
||||
code.add(' ');
|
||||
}
|
||||
if (mth.getAccessFlags().isConstructor()) {
|
||||
@@ -105,20 +110,19 @@ public class MethodGen {
|
||||
} else {
|
||||
LOG.warn(ErrorsCounter.formatErrorMsg(mth,
|
||||
"Incorrect number of args for enum constructor: " + args.size()
|
||||
+ " (expected >= 2)"));
|
||||
+ " (expected >= 2)"
|
||||
));
|
||||
}
|
||||
}
|
||||
code.add(makeArguments(args));
|
||||
code.add(")");
|
||||
addMethodArguments(code, args);
|
||||
code.add(')');
|
||||
|
||||
annotationGen.addThrows(mth, code);
|
||||
code.attachAnnotation(mth);
|
||||
code.attachDefinition(mth);
|
||||
return true;
|
||||
}
|
||||
|
||||
public CodeWriter makeArguments(List<RegisterArg> args) {
|
||||
CodeWriter argsCode = new CodeWriter();
|
||||
|
||||
private void addMethodArguments(CodeWriter argsCode, List<RegisterArg> args) {
|
||||
MethodParameters paramsAnnotation =
|
||||
(MethodParameters) mth.getAttributes().get(AttributeType.ANNOTATION_MTH_PARAMETERS);
|
||||
|
||||
@@ -127,9 +131,9 @@ public class MethodGen {
|
||||
RegisterArg arg = it.next();
|
||||
|
||||
// add argument annotation
|
||||
if (paramsAnnotation != null)
|
||||
if (paramsAnnotation != null) {
|
||||
annotationGen.addForParameter(argsCode, paramsAnnotation, i);
|
||||
|
||||
}
|
||||
if (!it.hasNext() && mth.getAccessFlags().isVarArgs()) {
|
||||
// change last array argument to varargs
|
||||
ArgType type = arg.getType();
|
||||
@@ -148,10 +152,10 @@ public class MethodGen {
|
||||
argsCode.add(makeArgName(arg));
|
||||
|
||||
i++;
|
||||
if (it.hasNext())
|
||||
if (it.hasNext()) {
|
||||
argsCode.add(", ");
|
||||
}
|
||||
}
|
||||
return argsCode;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -163,24 +167,23 @@ public class MethodGen {
|
||||
String name = arg.getTypedVar().getName();
|
||||
String base = "r" + arg.getRegNum();
|
||||
if (fallback) {
|
||||
if (name != null)
|
||||
if (name != null) {
|
||||
return base + "_" + name;
|
||||
else
|
||||
return base;
|
||||
}
|
||||
return base;
|
||||
} else {
|
||||
if (name != null) {
|
||||
if (name.equals("this"))
|
||||
return name;
|
||||
else if (Consts.DEBUG)
|
||||
return name + "_" + base;
|
||||
else
|
||||
return name;
|
||||
if (Consts.DEBUG) {
|
||||
return base + "_" + name;
|
||||
}
|
||||
return name;
|
||||
} else {
|
||||
ArgType type = arg.getType();
|
||||
if (type.isPrimitive())
|
||||
if (type.isPrimitive()) {
|
||||
return base + type.getPrimitiveType().getShortName().toLowerCase();
|
||||
else
|
||||
} else {
|
||||
return base + "_" + Utils.escape(TypeGen.translate(classGen, arg.getType()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -193,9 +196,9 @@ public class MethodGen {
|
||||
*/
|
||||
public String assignArg(RegisterArg arg) {
|
||||
String name = makeArgName(arg);
|
||||
if (varNames.add(name) || fallback)
|
||||
if (varNames.add(name) || fallback) {
|
||||
return name;
|
||||
|
||||
}
|
||||
name = getUniqVarName(name);
|
||||
arg.getTypedVar().setName(name);
|
||||
return name;
|
||||
@@ -203,9 +206,9 @@ public class MethodGen {
|
||||
|
||||
public String assignNamedArg(NamedArg arg) {
|
||||
String name = arg.getName();
|
||||
if (varNames.add(name) || fallback)
|
||||
if (varNames.add(name) || fallback) {
|
||||
return name;
|
||||
|
||||
}
|
||||
name = getUniqVarName(name);
|
||||
arg.setName(name);
|
||||
return name;
|
||||
@@ -222,16 +225,14 @@ public class MethodGen {
|
||||
return r;
|
||||
}
|
||||
|
||||
public CodeWriter makeInstructions(int mthIndent) throws CodegenException {
|
||||
CodeWriter code = new CodeWriter(mthIndent + 1);
|
||||
|
||||
public void addInstructions(CodeWriter code) throws CodegenException {
|
||||
if (mth.getAttributes().contains(AttributeType.JADX_ERROR)) {
|
||||
code.startLine("throw new UnsupportedOperationException(\"Method not decompiled: ");
|
||||
code.add(mth.toString());
|
||||
code.add("\");");
|
||||
|
||||
JadxErrorAttr err = (JadxErrorAttr) mth.getAttributes().get(AttributeType.JADX_ERROR);
|
||||
code.startLine("// jadx: method processing error");
|
||||
code.startLine("/* JADX: method processing error */");
|
||||
Throwable cause = err.getCause();
|
||||
if (cause != null) {
|
||||
code.newLine();
|
||||
@@ -240,51 +241,59 @@ public class MethodGen {
|
||||
code.add("*/");
|
||||
}
|
||||
makeMethodDump(code);
|
||||
} else if (mth.getAttributes().contains(AttributeFlag.INCONSISTENT_CODE)) {
|
||||
code.startLine("/*");
|
||||
addFallbackMethodCode(code);
|
||||
code.startLine("*/");
|
||||
code.newLine();
|
||||
} else {
|
||||
if (mth.getRegion() != null) {
|
||||
CodeWriter insns = new CodeWriter(mthIndent + 1);
|
||||
(new RegionGen(this, mth)).makeRegion(insns, mth.getRegion());
|
||||
code.add(insns);
|
||||
Region startRegion = mth.getRegion();
|
||||
if (startRegion != null) {
|
||||
(new RegionGen(this)).makeRegion(code, startRegion);
|
||||
} else {
|
||||
makeFallbackMethod(code, mth);
|
||||
addFallbackMethodCode(code);
|
||||
}
|
||||
}
|
||||
return code;
|
||||
}
|
||||
|
||||
public void makeMethodDump(CodeWriter code) {
|
||||
private void makeMethodDump(CodeWriter code) {
|
||||
code.startLine("/*");
|
||||
getFallbackMethodGen(mth).addDefinition(code);
|
||||
code.add(" {");
|
||||
code.incIndent();
|
||||
|
||||
makeFallbackMethod(code, mth);
|
||||
addFallbackMethodCode(code);
|
||||
|
||||
code.decIndent();
|
||||
code.startLine('}');
|
||||
code.startLine("*/");
|
||||
}
|
||||
|
||||
private void makeFallbackMethod(CodeWriter code, MethodNode mth) {
|
||||
public void addFallbackMethodCode(CodeWriter code) {
|
||||
if (mth.getInstructions() == null) {
|
||||
// loadFile original instructions
|
||||
// load original instructions
|
||||
try {
|
||||
mth.load();
|
||||
DepthTraverser.visit(new FallbackModeVisitor(), mth);
|
||||
DepthTraversal.visit(new FallbackModeVisitor(), mth);
|
||||
} catch (DecodeException e) {
|
||||
// ignore
|
||||
code.startLine("Can't loadFile method instructions");
|
||||
LOG.error("Error reload instructions in fallback mode:", e);
|
||||
code.startLine("// Can't loadFile method instructions: " + e.getMessage());
|
||||
return;
|
||||
}
|
||||
}
|
||||
List<InsnNode> insns = mth.getInstructions();
|
||||
if (insns == null) {
|
||||
code.startLine("// Can't load method instructions.");
|
||||
return;
|
||||
}
|
||||
if (mth.getThisArg() != null) {
|
||||
code.startLine(getFallbackMethodGen(mth).makeArgName(mth.getThisArg())).add(" = this;");
|
||||
}
|
||||
makeFallbackInsns(code, mth, mth.getInstructions(), true);
|
||||
addFallbackInsns(code, mth, insns, true);
|
||||
}
|
||||
|
||||
public static void makeFallbackInsns(CodeWriter code, MethodNode mth, List<InsnNode> insns, boolean addLabels) {
|
||||
InsnGen insnGen = new InsnGen(getFallbackMethodGen(mth), mth, true);
|
||||
public static void addFallbackInsns(CodeWriter code, MethodNode mth, List<InsnNode> insns, boolean addLabels) {
|
||||
InsnGen insnGen = new InsnGen(getFallbackMethodGen(mth), true);
|
||||
for (InsnNode insn : insns) {
|
||||
AttributesList attrs = insn.getAttributes();
|
||||
if (addLabels) {
|
||||
@@ -298,8 +307,9 @@ public class MethodGen {
|
||||
try {
|
||||
if (insnGen.makeInsn(insn, code)) {
|
||||
CatchAttr catchAttr = (CatchAttr) attrs.get(AttributeType.CATCH_BLOCK);
|
||||
if (catchAttr != null)
|
||||
if (catchAttr != null) {
|
||||
code.add("\t //" + catchAttr);
|
||||
}
|
||||
}
|
||||
} catch (CodegenException e) {
|
||||
code.startLine("// error: " + insn);
|
||||
|
||||
@@ -2,26 +2,19 @@ package jadx.core.codegen;
|
||||
|
||||
import jadx.core.dex.attributes.AttributeFlag;
|
||||
import jadx.core.dex.attributes.AttributeType;
|
||||
import jadx.core.dex.attributes.DeclareVariableAttr;
|
||||
import jadx.core.dex.attributes.DeclareVariablesAttr;
|
||||
import jadx.core.dex.attributes.ForceReturnAttr;
|
||||
import jadx.core.dex.attributes.IAttribute;
|
||||
import jadx.core.dex.info.FieldInfo;
|
||||
import jadx.core.dex.instructions.ArithNode;
|
||||
import jadx.core.dex.instructions.IfOp;
|
||||
import jadx.core.dex.instructions.IndexInsnNode;
|
||||
import jadx.core.dex.instructions.InsnType;
|
||||
import jadx.core.dex.instructions.SwitchNode;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.InsnWrapArg;
|
||||
import jadx.core.dex.instructions.args.LiteralArg;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.dex.nodes.IBlock;
|
||||
import jadx.core.dex.nodes.IContainer;
|
||||
import jadx.core.dex.nodes.IRegion;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.regions.IfCondition;
|
||||
import jadx.core.dex.regions.IfRegion;
|
||||
import jadx.core.dex.regions.LoopRegion;
|
||||
@@ -31,7 +24,6 @@ import jadx.core.dex.regions.SynchronizedRegion;
|
||||
import jadx.core.dex.trycatch.CatchAttr;
|
||||
import jadx.core.dex.trycatch.ExceptionHandler;
|
||||
import jadx.core.dex.trycatch.TryCatchBlock;
|
||||
import jadx.core.utils.ErrorsCounter;
|
||||
import jadx.core.utils.RegionUtils;
|
||||
import jadx.core.utils.exceptions.CodegenException;
|
||||
|
||||
@@ -43,29 +35,19 @@ import org.slf4j.LoggerFactory;
|
||||
public class RegionGen extends InsnGen {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(RegionGen.class);
|
||||
|
||||
public RegionGen(MethodGen mgen, MethodNode mth) {
|
||||
super(mgen, mth, false);
|
||||
public RegionGen(MethodGen mgen) {
|
||||
super(mgen, false);
|
||||
}
|
||||
|
||||
public void makeRegion(CodeWriter code, IContainer cont) throws CodegenException {
|
||||
assert cont != null;
|
||||
|
||||
if (cont instanceof IBlock) {
|
||||
makeSimpleBlock((IBlock) cont, code);
|
||||
} else if (cont instanceof IRegion) {
|
||||
declareVars(code, cont);
|
||||
if (cont instanceof Region) {
|
||||
Region r = (Region) cont;
|
||||
CatchAttr tc = (CatchAttr) r.getAttributes().get(AttributeType.CATCH_BLOCK);
|
||||
if (tc != null) {
|
||||
makeTryCatch(cont, tc.getTryBlock(), code);
|
||||
} else {
|
||||
for (IContainer c : r.getSubBlocks())
|
||||
makeRegion(code, c);
|
||||
}
|
||||
makeSimpleRegion(code, (Region) cont);
|
||||
} else if (cont instanceof IfRegion) {
|
||||
code.startLine();
|
||||
makeIf((IfRegion) cont, code);
|
||||
makeIf((IfRegion) cont, code, true);
|
||||
} else if (cont instanceof SwitchRegion) {
|
||||
makeSwitch((SwitchRegion) cont, code);
|
||||
} else if (cont instanceof LoopRegion) {
|
||||
@@ -74,16 +56,29 @@ public class RegionGen extends InsnGen {
|
||||
makeSynchronizedRegion((SynchronizedRegion) cont, code);
|
||||
}
|
||||
} else {
|
||||
throw new CodegenException("Not processed container: " + cont.toString());
|
||||
throw new CodegenException("Not processed container: " + cont);
|
||||
}
|
||||
}
|
||||
|
||||
private void declareVars(CodeWriter code, IContainer cont) {
|
||||
DeclareVariableAttr declVars =
|
||||
(DeclareVariableAttr) cont.getAttributes().get(AttributeType.DECLARE_VARIABLE);
|
||||
DeclareVariablesAttr declVars =
|
||||
(DeclareVariablesAttr) cont.getAttributes().get(AttributeType.DECLARE_VARIABLES);
|
||||
if (declVars != null) {
|
||||
for (RegisterArg v : declVars.getVars()) {
|
||||
code.startLine(declareVar(v)).add(';');
|
||||
code.startLine();
|
||||
declareVar(code, v);
|
||||
code.add(';');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void makeSimpleRegion(CodeWriter code, Region region) throws CodegenException {
|
||||
CatchAttr tc = (CatchAttr) region.getAttributes().get(AttributeType.CATCH_BLOCK);
|
||||
if (tc != null) {
|
||||
makeTryCatch(region, tc.getTryBlock(), code);
|
||||
} else {
|
||||
for (IContainer c : region.getSubBlocks()) {
|
||||
makeRegion(code, c);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -98,42 +93,57 @@ public class RegionGen extends InsnGen {
|
||||
for (InsnNode insn : block.getInstructions()) {
|
||||
makeInsn(insn, code);
|
||||
}
|
||||
if (block.getAttributes().contains(AttributeFlag.BREAK)) {
|
||||
code.startLine("break;");
|
||||
} else {
|
||||
IAttribute attr;
|
||||
if ((attr = block.getAttributes().get(AttributeType.FORCE_RETURN)) != null) {
|
||||
ForceReturnAttr retAttr = (ForceReturnAttr) attr;
|
||||
makeInsn(retAttr.getReturnInsn(), code);
|
||||
}
|
||||
IAttribute attr;
|
||||
if ((attr = block.getAttributes().get(AttributeType.FORCE_RETURN)) != null) {
|
||||
ForceReturnAttr retAttr = (ForceReturnAttr) attr;
|
||||
makeInsn(retAttr.getReturnInsn(), code);
|
||||
}
|
||||
}
|
||||
|
||||
private void makeIf(IfRegion region, CodeWriter code) throws CodegenException {
|
||||
code.add("if (").add(makeCondition(region.getCondition())).add(") {");
|
||||
private void makeIf(IfRegion region, CodeWriter code, boolean newLine) throws CodegenException {
|
||||
if (region.getTernRegion() != null) {
|
||||
makeSimpleBlock(region.getTernRegion().getBlock(), code);
|
||||
return;
|
||||
}
|
||||
if (newLine) {
|
||||
code.startLine();
|
||||
}
|
||||
code.add("if (");
|
||||
new ConditionGen(this).add(code, region.getCondition());
|
||||
code.add(") {");
|
||||
makeRegionIndent(code, region.getThenRegion());
|
||||
code.startLine('}');
|
||||
|
||||
IContainer els = region.getElseRegion();
|
||||
if (els != null && RegionUtils.notEmpty(els)) {
|
||||
code.add(" else ");
|
||||
|
||||
// connect if-else-if block
|
||||
if (els instanceof Region) {
|
||||
Region re = (Region) els;
|
||||
List<IContainer> subBlocks = re.getSubBlocks();
|
||||
if (subBlocks.size() == 1 && subBlocks.get(0) instanceof IfRegion) {
|
||||
makeIf((IfRegion) subBlocks.get(0), code);
|
||||
return;
|
||||
}
|
||||
if (connectElseIf(code, els)) {
|
||||
return;
|
||||
}
|
||||
|
||||
code.add('{');
|
||||
makeRegionIndent(code, els);
|
||||
code.startLine('}');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect if-else-if block
|
||||
*/
|
||||
private boolean connectElseIf(CodeWriter code, IContainer els) throws CodegenException {
|
||||
if (els instanceof Region) {
|
||||
Region re = (Region) els;
|
||||
List<IContainer> subBlocks = re.getSubBlocks();
|
||||
if (subBlocks.size() == 1 && subBlocks.get(0) instanceof IfRegion) {
|
||||
IfRegion ifRegion = (IfRegion) subBlocks.get(0);
|
||||
if (ifRegion.getAttributes().contains(AttributeFlag.ELSE_IF_CHAIN)) {
|
||||
makeIf(ifRegion, code, false);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private CodeWriter makeLoop(LoopRegion region, CodeWriter code) throws CodegenException {
|
||||
BlockNode header = region.getHeader();
|
||||
if (header != null) {
|
||||
@@ -141,7 +151,8 @@ public class RegionGen extends InsnGen {
|
||||
if (headerInsns.size() > 1) {
|
||||
// write not inlined instructions from header
|
||||
mth.getAttributes().add(AttributeFlag.INCONSISTENT_CODE);
|
||||
for (int i = 0; i < headerInsns.size() - 1; i++) {
|
||||
int last = headerInsns.size() - 1;
|
||||
for (int i = 0; i < last; i++) {
|
||||
InsnNode insn = headerInsns.get(i);
|
||||
makeInsn(insn, code);
|
||||
}
|
||||
@@ -157,12 +168,17 @@ public class RegionGen extends InsnGen {
|
||||
return code;
|
||||
}
|
||||
|
||||
ConditionGen conditionGen = new ConditionGen(this);
|
||||
if (region.isConditionAtEnd()) {
|
||||
code.startLine("do {");
|
||||
makeRegionIndent(code, region.getBody());
|
||||
code.startLine("} while (").add(makeCondition(condition)).add(");");
|
||||
code.startLine("} while (");
|
||||
conditionGen.add(code, condition);
|
||||
code.add(");");
|
||||
} else {
|
||||
code.startLine("while (").add(makeCondition(condition)).add(") {");
|
||||
code.startLine("while (");
|
||||
conditionGen.add(code, condition);
|
||||
code.add(") {");
|
||||
makeRegionIndent(code, region.getBody());
|
||||
code.startLine('}');
|
||||
}
|
||||
@@ -170,84 +186,20 @@ public class RegionGen extends InsnGen {
|
||||
}
|
||||
|
||||
private void makeSynchronizedRegion(SynchronizedRegion cont, CodeWriter code) throws CodegenException {
|
||||
code.startLine("synchronized(").add(arg(cont.getInsn().getArg(0))).add(") {");
|
||||
code.startLine("synchronized (");
|
||||
addArg(code, cont.getEnterInsn().getArg(0));
|
||||
code.add(") {");
|
||||
makeRegionIndent(code, cont.getRegion());
|
||||
code.startLine('}');
|
||||
}
|
||||
|
||||
private String makeCondition(IfCondition condition) throws CodegenException {
|
||||
switch (condition.getMode()) {
|
||||
case COMPARE:
|
||||
return makeCompare(condition.getCompare());
|
||||
case NOT:
|
||||
return "!" + makeCondition(condition.getArgs().get(0));
|
||||
case AND:
|
||||
case OR:
|
||||
String mode = condition.getMode() == IfCondition.Mode.AND ? " && " : " || ";
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (IfCondition arg : condition.getArgs()) {
|
||||
if (sb.length() != 0) {
|
||||
sb.append(mode);
|
||||
}
|
||||
String s = makeCondition(arg);
|
||||
if (arg.isCompare()) {
|
||||
sb.append(s);
|
||||
} else {
|
||||
sb.append('(').append(s).append(')');
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
default:
|
||||
return "??" + condition.toString();
|
||||
}
|
||||
}
|
||||
|
||||
private String makeCompare(IfCondition.Compare compare) throws CodegenException {
|
||||
IfOp op = compare.getOp();
|
||||
InsnArg firstArg = compare.getA();
|
||||
InsnArg secondArg = compare.getB();
|
||||
if (firstArg.getType().equals(ArgType.BOOLEAN)
|
||||
&& secondArg.isLiteral()
|
||||
&& secondArg.getType().equals(ArgType.BOOLEAN)) {
|
||||
LiteralArg lit = (LiteralArg) secondArg;
|
||||
if (lit.getLiteral() == 0) {
|
||||
op = op.invert();
|
||||
}
|
||||
if (op == IfOp.EQ) {
|
||||
return arg(firstArg, false); // == true
|
||||
} else if (op == IfOp.NE) {
|
||||
return "!" + arg(firstArg); // != true
|
||||
}
|
||||
LOG.warn(ErrorsCounter.formatErrorMsg(mth, "Unsupported boolean condition " + op.getSymbol()));
|
||||
}
|
||||
return arg(firstArg, isWrapNeeded(firstArg))
|
||||
+ " " + op.getSymbol() + " "
|
||||
+ arg(secondArg, isWrapNeeded(secondArg));
|
||||
}
|
||||
|
||||
private boolean isWrapNeeded(InsnArg arg) {
|
||||
if (!arg.isInsnWrap()) {
|
||||
return false;
|
||||
}
|
||||
InsnNode insn = ((InsnWrapArg) arg).getWrapInsn();
|
||||
if (insn.getType() == InsnType.ARITH) {
|
||||
ArithNode arith = ((ArithNode) insn);
|
||||
switch (arith.getOp()) {
|
||||
case ADD:
|
||||
case SUB:
|
||||
case MUL:
|
||||
case DIV:
|
||||
case REM:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private CodeWriter makeSwitch(SwitchRegion sw, CodeWriter code) throws CodegenException {
|
||||
SwitchNode insn = (SwitchNode) sw.getHeader().getInstructions().get(0);
|
||||
InsnArg arg = insn.getArg(0);
|
||||
code.startLine("switch(").add(arg(arg)).add(") {");
|
||||
code.startLine("switch (");
|
||||
addArg(code, arg);
|
||||
code.add(") {");
|
||||
code.incIndent();
|
||||
|
||||
int size = sw.getKeys().size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
@@ -256,9 +208,8 @@ public class RegionGen extends InsnGen {
|
||||
for (Object k : keys) {
|
||||
code.startLine("case ");
|
||||
if (k instanceof IndexInsnNode) {
|
||||
code.add(sfield((FieldInfo) ((IndexInsnNode) k).getIndex()));
|
||||
}
|
||||
else {
|
||||
code.add(staticField((FieldInfo) ((IndexInsnNode) k).getIndex()));
|
||||
} else {
|
||||
code.add(TypeGen.literalToString((Integer) k, arg.getType()));
|
||||
}
|
||||
code.add(':');
|
||||
@@ -269,19 +220,21 @@ public class RegionGen extends InsnGen {
|
||||
code.startLine("default:");
|
||||
makeCaseBlock(sw.getDefaultCase(), code);
|
||||
}
|
||||
|
||||
code.decIndent();
|
||||
code.startLine('}');
|
||||
return code;
|
||||
}
|
||||
|
||||
private void makeCaseBlock(IContainer c, CodeWriter code) throws CodegenException {
|
||||
boolean addBreak = true;
|
||||
if (RegionUtils.notEmpty(c)) {
|
||||
makeRegionIndent(code, c);
|
||||
if (RegionUtils.hasExitEdge(c)) {
|
||||
code.startLine(1, "break;");
|
||||
if (!RegionUtils.hasExitEdge(c)) {
|
||||
addBreak = false;
|
||||
}
|
||||
} else {
|
||||
code.startLine(1, "break;");
|
||||
}
|
||||
if (addBreak) {
|
||||
code.startLine().addIndent().add("break;");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -295,8 +248,9 @@ public class RegionGen extends InsnGen {
|
||||
if (!handler.isCatchAll()) {
|
||||
makeCatchBlock(code, handler);
|
||||
} else {
|
||||
if (allHandler != null)
|
||||
if (allHandler != null) {
|
||||
LOG.warn("Several 'all' handlers in try/catch block in " + mth);
|
||||
}
|
||||
allHandler = handler;
|
||||
}
|
||||
}
|
||||
@@ -313,7 +267,7 @@ public class RegionGen extends InsnGen {
|
||||
private void makeCatchBlock(CodeWriter code, ExceptionHandler handler)
|
||||
throws CodegenException {
|
||||
IContainer region = handler.getHandlerRegion();
|
||||
if (region != null /* && RegionUtils.notEmpty(region) */) {
|
||||
if (region != null) {
|
||||
code.startLine("} catch (");
|
||||
code.add(handler.isCatchAll() ? "Throwable" : useClass(handler.getCatchType()));
|
||||
code.add(' ');
|
||||
|
||||
@@ -10,9 +10,9 @@ public class TypeGen {
|
||||
|
||||
public static String translate(ClassGen clsGen, ArgType type) {
|
||||
final PrimitiveType stype = type.getPrimitiveType();
|
||||
if (stype == null)
|
||||
if (stype == null) {
|
||||
return type.toString();
|
||||
|
||||
}
|
||||
if (stype == PrimitiveType.OBJECT) {
|
||||
return clsGen.useClass(type);
|
||||
}
|
||||
@@ -69,8 +69,9 @@ public class TypeGen {
|
||||
|
||||
case OBJECT:
|
||||
case ARRAY:
|
||||
if (lit != 0)
|
||||
if (lit != 0) {
|
||||
throw new JadxRuntimeException("Wrong object literal: " + type + " = " + lit);
|
||||
}
|
||||
return "null";
|
||||
|
||||
default:
|
||||
@@ -100,8 +101,9 @@ public class TypeGen {
|
||||
|
||||
public static String formatLong(long lit) {
|
||||
String l = Long.toString(lit);
|
||||
if (lit == Long.MIN_VALUE || Math.abs(lit) >= Integer.MAX_VALUE)
|
||||
if (lit == Long.MIN_VALUE || Math.abs(lit) >= Integer.MAX_VALUE) {
|
||||
l += "L";
|
||||
}
|
||||
return wrapNegNum(lit < 0, l);
|
||||
}
|
||||
|
||||
@@ -109,6 +111,6 @@ public class TypeGen {
|
||||
// if (lz)
|
||||
// return "(" + str + ")";
|
||||
// else
|
||||
return str;
|
||||
return str;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +61,8 @@ public class NameMapper {
|
||||
"void",
|
||||
"volatile",
|
||||
"while",
|
||||
}));
|
||||
})
|
||||
);
|
||||
|
||||
public static boolean isReserved(String str) {
|
||||
return RESERVED_NAMES.contains(str);
|
||||
|
||||
@@ -6,8 +6,9 @@ public abstract class AttrNode implements IAttributeNode {
|
||||
|
||||
@Override
|
||||
public AttributesList getAttributes() {
|
||||
if (attributesList == null)
|
||||
if (attributesList == null) {
|
||||
attributesList = new AttributesList();
|
||||
}
|
||||
return attributesList;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,9 +9,11 @@ public enum AttributeFlag {
|
||||
|
||||
SYNTHETIC,
|
||||
|
||||
BREAK,
|
||||
RETURN, // block contains only return instruction
|
||||
|
||||
DECLARE_VAR,
|
||||
DONT_WRAP,
|
||||
|
||||
DONT_SHRINK,
|
||||
DONT_GENERATE,
|
||||
SKIP,
|
||||
@@ -19,5 +21,7 @@ public enum AttributeFlag {
|
||||
SKIP_FIRST_ARG,
|
||||
ANONYMOUS_CONSTRUCTOR,
|
||||
|
||||
ELSE_IF_CHAIN,
|
||||
|
||||
INCONSISTENT_CODE, // warning about incorrect decompilation
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ public enum AttributeType {
|
||||
|
||||
JADX_ERROR(true),
|
||||
METHOD_INLINE(true),
|
||||
FIELD_REPLACE(true),
|
||||
|
||||
ENUM_CLASS(true),
|
||||
|
||||
@@ -27,19 +28,25 @@ public enum AttributeType {
|
||||
|
||||
SOURCE_FILE(true),
|
||||
|
||||
DECLARE_VARIABLE(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())
|
||||
if (type.notUniq()) {
|
||||
last = i;
|
||||
}
|
||||
}
|
||||
NOT_UNIQ_COUNT = last + 1;
|
||||
}
|
||||
@@ -48,10 +55,6 @@ public enum AttributeType {
|
||||
return NOT_UNIQ_COUNT;
|
||||
}
|
||||
|
||||
private AttributeType(boolean isUniq) {
|
||||
this.uniq = isUniq;
|
||||
}
|
||||
|
||||
public boolean isUniq() {
|
||||
return uniq;
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import java.util.Collections;
|
||||
import java.util.EnumMap;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
@@ -30,7 +31,7 @@ public final class AttributesList {
|
||||
public AttributesList() {
|
||||
flags = EnumSet.noneOf(AttributeFlag.class);
|
||||
uniqAttr = new EnumMap<AttributeType, IAttribute>(AttributeType.class);
|
||||
attributes = new ArrayList<IAttribute>(0);
|
||||
attributes = new LinkedList<IAttribute>();
|
||||
attrCount = new int[AttributeType.getNotUniqCount()];
|
||||
}
|
||||
|
||||
@@ -51,10 +52,11 @@ public final class AttributesList {
|
||||
// Attributes
|
||||
|
||||
public void add(IAttribute attr) {
|
||||
if (attr.getType().isUniq())
|
||||
if (attr.getType().isUniq()) {
|
||||
uniqAttr.put(attr.getType(), attr);
|
||||
else
|
||||
} else {
|
||||
addMultiAttribute(attr);
|
||||
}
|
||||
}
|
||||
|
||||
private void addMultiAttribute(IAttribute attr) {
|
||||
@@ -69,15 +71,17 @@ public final class AttributesList {
|
||||
public void addAll(AttributesList otherList) {
|
||||
flags.addAll(otherList.flags);
|
||||
uniqAttr.putAll(otherList.uniqAttr);
|
||||
for (IAttribute attr : otherList.attributes)
|
||||
for (IAttribute attr : otherList.attributes) {
|
||||
addMultiAttribute(attr);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean contains(AttributeType type) {
|
||||
if (type.isUniq())
|
||||
if (type.isUniq()) {
|
||||
return uniqAttr.containsKey(type);
|
||||
else
|
||||
} else {
|
||||
return getMultiCountInternal(type) != 0;
|
||||
}
|
||||
}
|
||||
|
||||
public IAttribute get(AttributeType type) {
|
||||
@@ -85,9 +89,11 @@ public final class AttributesList {
|
||||
return uniqAttr.get(type);
|
||||
} else {
|
||||
if (getMultiCountInternal(type) != 0) {
|
||||
for (IAttribute attr : attributes)
|
||||
if (attr.getType() == type)
|
||||
for (IAttribute attr : attributes) {
|
||||
if (attr.getType() == type) {
|
||||
return attr;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -103,10 +109,7 @@ public final class AttributesList {
|
||||
|
||||
public Annotation getAnnotation(String cls) {
|
||||
AnnotationsList aList = (AnnotationsList) get(AttributeType.ANNOTATION_LIST);
|
||||
if (aList == null || aList.size() == 0)
|
||||
return null;
|
||||
|
||||
return aList.get(cls);
|
||||
return aList == null ? null : aList.get(cls);
|
||||
}
|
||||
|
||||
public List<IAttribute> getAll(AttributeType type) {
|
||||
@@ -118,8 +121,9 @@ public final class AttributesList {
|
||||
} else {
|
||||
List<IAttribute> attrs = new ArrayList<IAttribute>(count);
|
||||
for (IAttribute attr : attributes) {
|
||||
if (attr.getType() == type)
|
||||
if (attr.getType() == type) {
|
||||
attrs.add(attr);
|
||||
}
|
||||
}
|
||||
return attrs;
|
||||
}
|
||||
@@ -131,8 +135,9 @@ public final class AttributesList {
|
||||
} else {
|
||||
for (Iterator<IAttribute> it = attributes.iterator(); it.hasNext(); ) {
|
||||
IAttribute attr = it.next();
|
||||
if (attr.getType() == type)
|
||||
if (attr.getType() == type) {
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
attrCount[type.ordinal()] = 0;
|
||||
}
|
||||
@@ -142,11 +147,13 @@ public final class AttributesList {
|
||||
AttributeType type = attr.getType();
|
||||
if (type.isUniq()) {
|
||||
IAttribute a = uniqAttr.get(type);
|
||||
if (a == attr)
|
||||
if (a == attr) {
|
||||
uniqAttr.remove(type);
|
||||
}
|
||||
} else {
|
||||
if (getMultiCountInternal(type) == 0)
|
||||
if (getMultiCountInternal(type) == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (Iterator<IAttribute> it = attributes.iterator(); it.hasNext(); ) {
|
||||
IAttribute a = it.next();
|
||||
@@ -167,25 +174,28 @@ public final class AttributesList {
|
||||
|
||||
public List<String> getAttributeStrings() {
|
||||
int size = flags.size() + uniqAttr.size() + attributes.size();
|
||||
if (size == 0)
|
||||
if (size == 0) {
|
||||
return Collections.emptyList();
|
||||
|
||||
}
|
||||
List<String> list = new ArrayList<String>(size);
|
||||
for (AttributeFlag a : flags)
|
||||
for (AttributeFlag a : flags) {
|
||||
list.add(a.toString());
|
||||
for (IAttribute a : uniqAttr.values())
|
||||
}
|
||||
for (IAttribute a : uniqAttr.values()) {
|
||||
list.add(a.toString());
|
||||
for (IAttribute a : attributes)
|
||||
}
|
||||
for (IAttribute a : attributes) {
|
||||
list.add(a.toString());
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
List<String> list = getAttributeStrings();
|
||||
if (list.isEmpty())
|
||||
if (list.isEmpty()) {
|
||||
return "";
|
||||
|
||||
}
|
||||
return "A:{" + Utils.listToString(list) + "}";
|
||||
}
|
||||
|
||||
|
||||
@@ -45,9 +45,10 @@ public final class BlockRegState {
|
||||
StringBuilder str = new StringBuilder();
|
||||
for (RegisterArg reg : regs) {
|
||||
if (reg.getTypedVar() != null) {
|
||||
if (str.length() != 0)
|
||||
if (str.length() != 0) {
|
||||
str.append(", ");
|
||||
str.append(reg.toString());
|
||||
}
|
||||
str.append(reg);
|
||||
}
|
||||
}
|
||||
return str.toString();
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
package jadx.core.dex.attributes;
|
||||
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class DeclareVariableAttr implements IAttribute {
|
||||
|
||||
private final List<RegisterArg> vars;
|
||||
|
||||
public DeclareVariableAttr() {
|
||||
this.vars = null; // for instruction use result
|
||||
}
|
||||
|
||||
public DeclareVariableAttr(List<RegisterArg> vars) {
|
||||
this.vars = vars; // for regions
|
||||
}
|
||||
|
||||
public List<RegisterArg> getVars() {
|
||||
return vars;
|
||||
}
|
||||
|
||||
public void addVar(RegisterArg arg) {
|
||||
int i;
|
||||
if ((i = vars.indexOf(arg)) != -1) {
|
||||
if (vars.get(i).getType().equals(arg.getType()))
|
||||
return;
|
||||
}
|
||||
vars.add(arg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AttributeType getType() {
|
||||
return AttributeType.DECLARE_VARIABLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DECL_VAR: " + Utils.listToString(vars);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package jadx.core.dex.attributes;
|
||||
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* List of variables to be declared at region start.
|
||||
*/
|
||||
public class DeclareVariablesAttr implements IAttribute {
|
||||
|
||||
private final List<RegisterArg> vars = new LinkedList<RegisterArg>();
|
||||
|
||||
public Iterable<RegisterArg> getVars() {
|
||||
return vars;
|
||||
}
|
||||
|
||||
public void addVar(RegisterArg arg) {
|
||||
vars.add(arg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AttributeType getType() {
|
||||
return AttributeType.DECLARE_VARIABLES;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DECL_VAR: " + Utils.listToString(vars);
|
||||
}
|
||||
}
|
||||
@@ -18,10 +18,11 @@ public class EnumClassAttr implements IAttribute {
|
||||
|
||||
public EnumField(String name, int argsCount) {
|
||||
this.name = name;
|
||||
if (argsCount != 0)
|
||||
if (argsCount != 0) {
|
||||
this.args = new ArrayList<InsnArg>(argsCount);
|
||||
else
|
||||
} else {
|
||||
this.args = Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package jadx.core.dex.attributes;
|
||||
|
||||
import jadx.core.dex.info.FieldInfo;
|
||||
|
||||
public class FieldReplaceAttr implements IAttribute {
|
||||
|
||||
private final FieldInfo fieldInfo;
|
||||
private final boolean isOuterClass;
|
||||
|
||||
public FieldReplaceAttr(FieldInfo fieldInfo, boolean isOuterClass) {
|
||||
this.fieldInfo = fieldInfo;
|
||||
this.isOuterClass = isOuterClass;
|
||||
}
|
||||
|
||||
public FieldInfo getFieldInfo() {
|
||||
return fieldInfo;
|
||||
}
|
||||
|
||||
public boolean isOuterClass() {
|
||||
return isOuterClass;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AttributeType getType() {
|
||||
return AttributeType.FIELD_REPLACE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "REPLACE: " + fieldInfo;
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@ public class JadxErrorAttr implements IAttribute {
|
||||
if (cause == null) {
|
||||
str.append("null");
|
||||
} else {
|
||||
str.append(cause.getClass().toString());
|
||||
str.append(cause.getClass());
|
||||
str.append(":");
|
||||
str.append(cause.getMessage());
|
||||
str.append("\n");
|
||||
|
||||
@@ -32,18 +32,20 @@ public class JumpAttribute implements IAttribute {
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + dest;
|
||||
result = prime * result + src;
|
||||
return result;
|
||||
return 31 * dest + src;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (obj == null) return false;
|
||||
if (getClass() != obj.getClass()) return false;
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
JumpAttribute other = (JumpAttribute) obj;
|
||||
return dest == other.dest && src == other.src;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
package jadx.core.dex.attributes;
|
||||
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.dex.nodes.Edge;
|
||||
import jadx.core.utils.BlockUtils;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class LoopAttr implements IAttribute {
|
||||
@@ -37,21 +40,39 @@ public class LoopAttr implements IAttribute {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return block nodes with exit edges from loop <br>
|
||||
* Return source blocks of exit edges. <br>
|
||||
* Exit nodes belongs to loop (contains in {@code loopBlocks})
|
||||
*/
|
||||
public Set<BlockNode> getExitNodes() {
|
||||
Set<BlockNode> nodes = new HashSet<BlockNode>();
|
||||
Set<BlockNode> inloop = getLoopBlocks();
|
||||
for (BlockNode block : inloop) {
|
||||
Set<BlockNode> blocks = getLoopBlocks();
|
||||
for (BlockNode block : blocks) {
|
||||
// exit: successor node not from this loop, (don't change to getCleanSuccessors)
|
||||
for (BlockNode s : block.getSuccessors())
|
||||
if (!inloop.contains(s) && !s.getAttributes().contains(AttributeType.EXC_HANDLER))
|
||||
for (BlockNode s : block.getSuccessors()) {
|
||||
if (!blocks.contains(s) && !s.getAttributes().contains(AttributeType.EXC_HANDLER)) {
|
||||
nodes.add(block);
|
||||
}
|
||||
}
|
||||
}
|
||||
return nodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return loop exit edges.
|
||||
*/
|
||||
public List<Edge> getExitEdges() {
|
||||
List<Edge> edges = new LinkedList<Edge>();
|
||||
Set<BlockNode> blocks = getLoopBlocks();
|
||||
for (BlockNode block : blocks) {
|
||||
for (BlockNode s : block.getSuccessors()) {
|
||||
if (!blocks.contains(s) && !s.getAttributes().contains(AttributeType.EXC_HANDLER)) {
|
||||
edges.add(new Edge(block, s));
|
||||
}
|
||||
}
|
||||
}
|
||||
return edges;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "LOOP: " + start + "->" + end;
|
||||
|
||||
@@ -24,10 +24,11 @@ public class AccessInfo {
|
||||
}
|
||||
|
||||
public AccessInfo remove(int flag) {
|
||||
if (containsFlag(flag))
|
||||
if (containsFlag(flag)) {
|
||||
return new AccessInfo(accFlags - flag, type);
|
||||
else
|
||||
} else {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public AccessInfo getVisibility() {
|
||||
@@ -111,71 +112,73 @@ public class AccessInfo {
|
||||
|
||||
public String makeString() {
|
||||
StringBuilder code = new StringBuilder();
|
||||
if (isPublic())
|
||||
if (isPublic()) {
|
||||
code.append("public ");
|
||||
|
||||
if (isPrivate())
|
||||
}
|
||||
if (isPrivate()) {
|
||||
code.append("private ");
|
||||
|
||||
if (isProtected())
|
||||
}
|
||||
if (isProtected()) {
|
||||
code.append("protected ");
|
||||
|
||||
if (isStatic())
|
||||
}
|
||||
if (isStatic()) {
|
||||
code.append("static ");
|
||||
|
||||
if (isFinal())
|
||||
}
|
||||
if (isFinal()) {
|
||||
code.append("final ");
|
||||
|
||||
if (isAbstract())
|
||||
}
|
||||
if (isAbstract()) {
|
||||
code.append("abstract ");
|
||||
|
||||
if (isNative())
|
||||
}
|
||||
if (isNative()) {
|
||||
code.append("native ");
|
||||
|
||||
}
|
||||
switch (type) {
|
||||
case METHOD:
|
||||
if (isSynchronized())
|
||||
if (isSynchronized()) {
|
||||
code.append("synchronized ");
|
||||
|
||||
if (isBridge())
|
||||
}
|
||||
if (isBridge()) {
|
||||
code.append("/* bridge */ ");
|
||||
|
||||
}
|
||||
if (Consts.DEBUG) {
|
||||
if (isVarArgs())
|
||||
if (isVarArgs()) {
|
||||
code.append("/* varargs */ ");
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case FIELD:
|
||||
if (isVolatile())
|
||||
if (isVolatile()) {
|
||||
code.append("volatile ");
|
||||
|
||||
if (isTransient())
|
||||
}
|
||||
if (isTransient()) {
|
||||
code.append("transient ");
|
||||
}
|
||||
break;
|
||||
|
||||
case CLASS:
|
||||
if ((accFlags & AccessFlags.ACC_STRICT) != 0)
|
||||
if ((accFlags & AccessFlags.ACC_STRICT) != 0) {
|
||||
code.append("strict ");
|
||||
|
||||
}
|
||||
if (Consts.DEBUG) {
|
||||
if ((accFlags & AccessFlags.ACC_SUPER) != 0)
|
||||
if ((accFlags & AccessFlags.ACC_SUPER) != 0) {
|
||||
code.append("/* super */ ");
|
||||
|
||||
if ((accFlags & AccessFlags.ACC_ENUM) != 0)
|
||||
}
|
||||
if ((accFlags & AccessFlags.ACC_ENUM) != 0) {
|
||||
code.append("/* enum */ ");
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (isSynthetic())
|
||||
if (isSynthetic()) {
|
||||
code.append("/* synthetic */ ");
|
||||
|
||||
}
|
||||
return code.toString();
|
||||
}
|
||||
|
||||
public String rawString() {
|
||||
switch (type){
|
||||
switch (type) {
|
||||
case CLASS:
|
||||
return AccessFlags.classString(accFlags);
|
||||
case FIELD:
|
||||
|
||||
@@ -13,14 +13,28 @@ public final class ClassInfo {
|
||||
|
||||
private static final Map<ArgType, ClassInfo> CLASSINFO_CACHE = new WeakHashMap<ArgType, ClassInfo>();
|
||||
|
||||
private final ArgType type;
|
||||
private String pkg;
|
||||
private String name;
|
||||
private String fullName;
|
||||
// for inner class not equals null
|
||||
private ClassInfo parentClass;
|
||||
|
||||
private ClassInfo(ArgType type) {
|
||||
assert type.isObject() : "Not class type: " + type;
|
||||
this.type = type;
|
||||
|
||||
splitNames(true);
|
||||
}
|
||||
|
||||
public static ClassInfo fromDex(DexNode dex, int clsIndex) {
|
||||
if (clsIndex == DexNode.NO_INDEX)
|
||||
if (clsIndex == DexNode.NO_INDEX) {
|
||||
return null;
|
||||
|
||||
}
|
||||
ArgType type = dex.getType(clsIndex);
|
||||
if (type.isArray())
|
||||
if (type.isArray()) {
|
||||
type = ArgType.OBJECT;
|
||||
|
||||
}
|
||||
return fromType(type);
|
||||
}
|
||||
|
||||
@@ -41,54 +55,41 @@ public final class ClassInfo {
|
||||
CLASSINFO_CACHE.clear();
|
||||
}
|
||||
|
||||
private final ArgType type;
|
||||
private String pkg;
|
||||
private String name;
|
||||
private String fullName;
|
||||
private ClassInfo parentClass; // not equals null if this is inner class
|
||||
|
||||
private ClassInfo(ArgType type) {
|
||||
assert type.isObject() : "Not class type: " + type;
|
||||
this.type = type;
|
||||
|
||||
splitNames(true);
|
||||
}
|
||||
|
||||
private void splitNames(boolean canBeInner) {
|
||||
String fullObjectName = type.getObject();
|
||||
assert fullObjectName.indexOf('/') == -1 : "Raw type: " + type;
|
||||
|
||||
String name;
|
||||
String clsName;
|
||||
int dot = fullObjectName.lastIndexOf('.');
|
||||
if (dot == -1) {
|
||||
// rename default package if it used from class with package (often for obfuscated apps),
|
||||
pkg = Consts.DEFAULT_PACKAGE_NAME;
|
||||
name = fullObjectName;
|
||||
clsName = fullObjectName;
|
||||
} else {
|
||||
pkg = fullObjectName.substring(0, dot);
|
||||
name = fullObjectName.substring(dot + 1);
|
||||
clsName = fullObjectName.substring(dot + 1);
|
||||
}
|
||||
|
||||
int sep = name.lastIndexOf('$');
|
||||
if (canBeInner && sep > 0 && sep != name.length() - 1) {
|
||||
String parClsName = pkg + '.' + name.substring(0, sep);
|
||||
int sep = clsName.lastIndexOf('$');
|
||||
if (canBeInner && sep > 0 && sep != clsName.length() - 1) {
|
||||
String parClsName = pkg + "." + clsName.substring(0, sep);
|
||||
parentClass = fromName(parClsName);
|
||||
name = name.substring(sep + 1);
|
||||
clsName = clsName.substring(sep + 1);
|
||||
} else {
|
||||
parentClass = null;
|
||||
}
|
||||
|
||||
char firstChar = name.charAt(0);
|
||||
char firstChar = clsName.charAt(0);
|
||||
if (Character.isDigit(firstChar)) {
|
||||
name = Consts.ANONYMOUS_CLASS_PREFIX + name;
|
||||
clsName = Consts.ANONYMOUS_CLASS_PREFIX + clsName;
|
||||
} else if (firstChar == '$') {
|
||||
name = "_" + name;
|
||||
clsName = "_" + clsName;
|
||||
}
|
||||
if (NameMapper.isReserved(name)) {
|
||||
name += "_";
|
||||
if (NameMapper.isReserved(clsName)) {
|
||||
clsName += "_";
|
||||
}
|
||||
this.fullName = (parentClass != null ? parentClass.getFullName() : pkg) + "." + name;
|
||||
this.name = name;
|
||||
this.fullName = (parentClass != null ? parentClass.getFullName() : pkg) + "." + clsName;
|
||||
this.name = clsName;
|
||||
}
|
||||
|
||||
public String getFullPath() {
|
||||
@@ -153,7 +154,9 @@ public final class ClassInfo {
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj instanceof ClassInfo) {
|
||||
ClassInfo other = (ClassInfo) obj;
|
||||
return this.getFullName().equals(other.getFullName());
|
||||
|
||||
@@ -7,20 +7,22 @@ import com.android.dx.io.FieldId;
|
||||
|
||||
public class FieldInfo {
|
||||
|
||||
private final ClassInfo declClass;
|
||||
private final String name;
|
||||
private final ArgType type;
|
||||
|
||||
private final ClassInfo declClass;
|
||||
|
||||
public static FieldInfo fromDex(DexNode dex, int index) {
|
||||
return new FieldInfo(dex, index);
|
||||
public FieldInfo(ClassInfo declClass, String name, ArgType type) {
|
||||
this.declClass = declClass;
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
private FieldInfo(DexNode dex, int ind) {
|
||||
FieldId field = dex.getFieldId(ind);
|
||||
this.name = dex.getString(field.getNameIndex());
|
||||
this.type = dex.getType(field.getTypeIndex());
|
||||
this.declClass = ClassInfo.fromDex(dex, field.getDeclaringClassIndex());
|
||||
public static FieldInfo fromDex(DexNode dex, int index) {
|
||||
FieldId field = dex.getFieldId(index);
|
||||
return new FieldInfo(
|
||||
ClassInfo.fromDex(dex, field.getDeclaringClassIndex()),
|
||||
dex.getString(field.getNameIndex()),
|
||||
dex.getType(field.getTypeIndex()));
|
||||
}
|
||||
|
||||
public static String getNameById(DexNode dex, int ind) {
|
||||
@@ -41,13 +43,22 @@ public class FieldInfo {
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
FieldInfo fieldInfo = (FieldInfo) o;
|
||||
if (!name.equals(fieldInfo.name)) return false;
|
||||
if (!type.equals(fieldInfo.type)) return false;
|
||||
if (!declClass.equals(fieldInfo.declClass)) return false;
|
||||
if (!name.equals(fieldInfo.name)) {
|
||||
return false;
|
||||
}
|
||||
if (!type.equals(fieldInfo.type)) {
|
||||
return false;
|
||||
}
|
||||
if (!declClass.equals(fieldInfo.declClass)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -18,10 +18,6 @@ public final class MethodInfo {
|
||||
private final ClassInfo declClass;
|
||||
private final String shortId;
|
||||
|
||||
public static MethodInfo fromDex(DexNode dex, int mthIndex) {
|
||||
return new MethodInfo(dex, mthIndex);
|
||||
}
|
||||
|
||||
private MethodInfo(DexNode dex, int mthIndex) {
|
||||
MethodId mthId = dex.getMethodId(mthIndex);
|
||||
name = dex.getString(mthId.getNameIndex());
|
||||
@@ -31,14 +27,20 @@ public final class MethodInfo {
|
||||
retType = dex.getType(proto.getReturnTypeIndex());
|
||||
args = dex.readParamList(proto.getParametersOffset());
|
||||
|
||||
StringBuilder strArg = new StringBuilder();
|
||||
strArg.append('(');
|
||||
for (ArgType arg : args)
|
||||
strArg.append(TypeGen.signature(arg));
|
||||
strArg.append(')');
|
||||
// strArg.append(TypeGen.signature(retType));
|
||||
StringBuilder signature = new StringBuilder();
|
||||
signature.append(name);
|
||||
signature.append('(');
|
||||
for (ArgType arg : args) {
|
||||
signature.append(TypeGen.signature(arg));
|
||||
}
|
||||
signature.append(')');
|
||||
signature.append(TypeGen.signature(retType));
|
||||
|
||||
shortId = name + strArg;
|
||||
shortId = signature.toString();
|
||||
}
|
||||
|
||||
public static MethodInfo fromDex(DexNode dex, int mthIndex) {
|
||||
return new MethodInfo(dex, mthIndex);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
@@ -86,30 +88,40 @@ public final class MethodInfo {
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + declClass.hashCode();
|
||||
result = prime * result + retType.hashCode();
|
||||
result = prime * result + shortId.hashCode();
|
||||
int result = declClass.hashCode();
|
||||
result = 31 * result + retType.hashCode();
|
||||
result = 31 * result + shortId.hashCode();
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (obj == null) return false;
|
||||
if (getClass() != obj.getClass()) return false;
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
MethodInfo other = (MethodInfo) obj;
|
||||
if (!shortId.equals(other.shortId)) return false;
|
||||
if (!retType.equals(other.retType)) return false;
|
||||
if (!declClass.equals(other.declClass)) return false;
|
||||
if (!shortId.equals(other.shortId)) {
|
||||
return false;
|
||||
}
|
||||
if (!retType.equals(other.retType)) {
|
||||
return false;
|
||||
}
|
||||
if (!declClass.equals(other.declClass)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return retType + " " + declClass.getFullName() + "." + name
|
||||
+ "(" + Utils.listToString(args) + ")";
|
||||
return declClass.getFullName() + "." + name
|
||||
+ "(" + Utils.listToString(args) + "):" + retType;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ public class ArithNode extends InsnNode {
|
||||
}
|
||||
|
||||
public ArithNode(ArithOp op, RegisterArg res, InsnArg a) {
|
||||
super(InsnType.ARITH, 1);
|
||||
super(InsnType.ARITH_ONEARG, 1);
|
||||
this.op = op;
|
||||
setResult(res);
|
||||
addArg(a);
|
||||
|
||||
@@ -15,12 +15,12 @@ public enum ArithOp {
|
||||
SHR(">>"),
|
||||
USHR(">>>");
|
||||
|
||||
private final String symbol;
|
||||
|
||||
private ArithOp(String symbol) {
|
||||
this.symbol = symbol;
|
||||
}
|
||||
|
||||
private final String symbol;
|
||||
|
||||
public String getSymbol() {
|
||||
return this.symbol;
|
||||
}
|
||||
|
||||
@@ -8,11 +8,11 @@ public class GotoNode extends InsnNode {
|
||||
protected int target;
|
||||
|
||||
public GotoNode(int target) {
|
||||
this(InsnType.GOTO, target);
|
||||
this(InsnType.GOTO, target, 0);
|
||||
}
|
||||
|
||||
protected GotoNode(InsnType type, int target) {
|
||||
super(type);
|
||||
protected GotoNode(InsnType type, int target, int argsCount) {
|
||||
super(type, argsCount);
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ package jadx.core.dex.instructions;
|
||||
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.LiteralArg;
|
||||
import jadx.core.dex.instructions.args.PrimitiveType;
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.utils.InsnUtils;
|
||||
@@ -14,48 +13,32 @@ import static jadx.core.utils.BlockUtils.selectOther;
|
||||
|
||||
public class IfNode extends GotoNode {
|
||||
|
||||
protected boolean zeroCmp;
|
||||
private static final ArgType ARG_TYPE = ArgType.unknown(
|
||||
PrimitiveType.INT, PrimitiveType.OBJECT, PrimitiveType.ARRAY,
|
||||
PrimitiveType.BOOLEAN, PrimitiveType.SHORT, PrimitiveType.CHAR);
|
||||
|
||||
protected IfOp op;
|
||||
|
||||
private BlockNode thenBlock;
|
||||
private BlockNode elseBlock;
|
||||
|
||||
public IfNode(int targ, InsnArg then, InsnArg els) {
|
||||
super(InsnType.IF, targ);
|
||||
addArg(then);
|
||||
if (els == null) {
|
||||
zeroCmp = true;
|
||||
} else {
|
||||
zeroCmp = false;
|
||||
addArg(els);
|
||||
}
|
||||
public IfNode(DecodedInstruction insn, IfOp op) {
|
||||
this(op, insn.getTarget(),
|
||||
InsnArg.reg(insn, 0, ARG_TYPE),
|
||||
insn.getRegisterCount() == 1 ? InsnArg.lit(0, ARG_TYPE) : InsnArg.reg(insn, 1, ARG_TYPE));
|
||||
}
|
||||
|
||||
public IfNode(DecodedInstruction insn, IfOp op) {
|
||||
super(InsnType.IF, insn.getTarget());
|
||||
public IfNode(IfOp op, int targetOffset, InsnArg arg1, InsnArg arg2) {
|
||||
super(InsnType.IF, targetOffset, 2);
|
||||
this.op = op;
|
||||
|
||||
ArgType type = ArgType.unknown(
|
||||
PrimitiveType.INT, PrimitiveType.OBJECT, PrimitiveType.ARRAY,
|
||||
PrimitiveType.BOOLEAN, PrimitiveType.SHORT, PrimitiveType.CHAR);
|
||||
|
||||
addReg(insn, 0, type);
|
||||
if (insn.getRegisterCount() == 1) {
|
||||
zeroCmp = true;
|
||||
} else {
|
||||
zeroCmp = false;
|
||||
addReg(insn, 1, type);
|
||||
}
|
||||
addArg(arg1);
|
||||
addArg(arg2);
|
||||
}
|
||||
|
||||
public IfOp getOp() {
|
||||
return op;
|
||||
}
|
||||
|
||||
public boolean isZeroCmp() {
|
||||
return zeroCmp;
|
||||
}
|
||||
|
||||
public void invertCondition() {
|
||||
op = op.invert();
|
||||
BlockNode tmp = thenBlock;
|
||||
@@ -64,17 +47,10 @@ public class IfNode extends GotoNode {
|
||||
target = thenBlock.getStartOffset();
|
||||
}
|
||||
|
||||
public void changeCondition(InsnArg arg1, InsnArg arg2, IfOp op) {
|
||||
public void changeCondition(IfOp op, InsnArg arg1, InsnArg arg2) {
|
||||
this.op = op;
|
||||
this.zeroCmp = arg2.isLiteral() && ((LiteralArg) arg2).getLiteral() == 0;
|
||||
setArg(0, arg1);
|
||||
if (!zeroCmp) {
|
||||
if (getArgsCount() == 2) {
|
||||
setArg(1, arg2);
|
||||
} else {
|
||||
addArg(arg2);
|
||||
}
|
||||
}
|
||||
setArg(1, arg2);
|
||||
}
|
||||
|
||||
public void initBlocks(BlockNode curBlock) {
|
||||
@@ -84,7 +60,6 @@ public class IfNode extends GotoNode {
|
||||
} else {
|
||||
elseBlock = selectOther(thenBlock, curBlock.getSuccessors());
|
||||
}
|
||||
target = thenBlock.getStartOffset();
|
||||
}
|
||||
|
||||
public BlockNode getThenBlock() {
|
||||
@@ -99,8 +74,7 @@ public class IfNode extends GotoNode {
|
||||
public String toString() {
|
||||
return InsnUtils.formatOffset(offset) + ": "
|
||||
+ InsnUtils.insnTypeToString(insnType)
|
||||
+ getArg(0) + " " + op.getSymbol()
|
||||
+ " " + (zeroCmp ? "0" : getArg(1))
|
||||
+ getArg(0) + " " + op.getSymbol() + " " + getArg(1)
|
||||
+ " -> " + (thenBlock != null ? thenBlock : InsnUtils.formatOffset(target));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -473,7 +473,7 @@ public class InsnDecoder {
|
||||
case Opcodes.ARRAY_LENGTH: {
|
||||
InsnNode node = new InsnNode(InsnType.ARRAY_LENGTH, 1);
|
||||
node.setResult(InsnArg.reg(insn, 0, ArgType.INT));
|
||||
node.addArg(InsnArg.reg(insn, 1, ArgType.unknown(PrimitiveType.ARRAY)));
|
||||
node.addArg(InsnArg.reg(insn, 1, ArgType.array(ArgType.UNKNOWN)));
|
||||
return node;
|
||||
}
|
||||
|
||||
@@ -578,14 +578,16 @@ public class InsnDecoder {
|
||||
targets = ps.getTargets();
|
||||
keys = new Object[targets.length];
|
||||
int k = ps.getFirstKey();
|
||||
for (int i = 0; i < keys.length; i++)
|
||||
for (int i = 0; i < keys.length; i++) {
|
||||
keys[i] = k++;
|
||||
}
|
||||
} else {
|
||||
SparseSwitchPayloadDecodedInstruction ss = (SparseSwitchPayloadDecodedInstruction) payload;
|
||||
targets = ss.getTargets();
|
||||
keys = new Object[targets.length];
|
||||
for (int i = 0; i < keys.length; i++)
|
||||
for (int i = 0; i < keys.length; i++) {
|
||||
keys[i] = ss.getKeys()[i];
|
||||
}
|
||||
}
|
||||
// convert from relative to absolute offsets
|
||||
for (int i = 0; i < targets.length; i++) {
|
||||
@@ -612,8 +614,9 @@ public class InsnDecoder {
|
||||
r++;
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < insn.getRegisterCount(); i++)
|
||||
for (int i = 0; i < insn.getRegisterCount(); i++) {
|
||||
regs[i] = InsnArg.reg(insn, i, elType);
|
||||
}
|
||||
}
|
||||
return insn(InsnType.FILLED_NEW_ARRAY,
|
||||
resReg == -1 ? null : InsnArg.reg(resReg, arrType),
|
||||
@@ -689,8 +692,9 @@ public class InsnDecoder {
|
||||
InsnNode node = new InsnNode(type, args == null ? 0 : args.length);
|
||||
node.setResult(res);
|
||||
if (args != null) {
|
||||
for (InsnArg arg : args)
|
||||
for (InsnArg arg : args) {
|
||||
node.addArg(arg);
|
||||
}
|
||||
}
|
||||
return node;
|
||||
}
|
||||
@@ -711,19 +715,23 @@ public class InsnDecoder {
|
||||
|
||||
public static int getPrevInsnOffset(Object[] insnArr, int offset) {
|
||||
int i = offset - 1;
|
||||
while (i >= 0 && insnArr[i] == null)
|
||||
while (i >= 0 && insnArr[i] == null) {
|
||||
i--;
|
||||
if (i < 0)
|
||||
}
|
||||
if (i < 0) {
|
||||
return -1;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
public static int getNextInsnOffset(Object[] insnArr, int offset) {
|
||||
int i = offset + 1;
|
||||
while (i < insnArr.length && insnArr[i] == null)
|
||||
while (i < insnArr.length && insnArr[i] == null) {
|
||||
i++;
|
||||
if (i >= insnArr.length)
|
||||
}
|
||||
if (i >= insnArr.length) {
|
||||
return -1;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,6 +54,7 @@ public enum InsnType {
|
||||
CONTINUE,
|
||||
|
||||
STR_CONCAT, // strings concatenation
|
||||
ARITH_ONEARG,
|
||||
|
||||
TERNARY,
|
||||
ARGS, // just generate arguments
|
||||
|
||||
@@ -19,8 +19,9 @@ public class InvokeNode extends InsnNode {
|
||||
this.mth = mth;
|
||||
this.type = type;
|
||||
|
||||
if (resReg >= 0)
|
||||
if (resReg >= 0) {
|
||||
setResult(InsnArg.reg(resReg, mth.getReturnType()));
|
||||
}
|
||||
|
||||
int k = isRange ? insn.getA() : 0;
|
||||
if (type != InvokeType.STATIC) {
|
||||
|
||||
@@ -42,8 +42,9 @@ public class SwitchNode extends InsnNode {
|
||||
targ.append('[');
|
||||
for (int i = 0; i < targets.length; i++) {
|
||||
targ.append(InsnUtils.formatOffset(targets[i]));
|
||||
if (i < targets.length - 1)
|
||||
if (i < targets.length - 1) {
|
||||
targ.append(", ");
|
||||
}
|
||||
}
|
||||
targ.append(']');
|
||||
return super.toString() + " k:" + Arrays.toString(keys) + " t:" + targ;
|
||||
|
||||
@@ -2,20 +2,14 @@ package jadx.core.dex.instructions.args;
|
||||
|
||||
import jadx.core.Consts;
|
||||
import jadx.core.clsp.ClspGraph;
|
||||
import jadx.core.dex.nodes.parser.SignatureParser;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public abstract class ArgType {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ArgType.class);
|
||||
|
||||
public static final ArgType INT = primitive(PrimitiveType.INT);
|
||||
public static final ArgType BOOLEAN = primitive(PrimitiveType.BOOLEAN);
|
||||
@@ -59,19 +53,31 @@ public abstract class ArgType {
|
||||
}
|
||||
|
||||
public static ArgType object(String obj) {
|
||||
return new ObjectArg(obj);
|
||||
return new ObjectType(obj);
|
||||
}
|
||||
|
||||
public static ArgType genericType(String type) {
|
||||
return new GenericTypeArg(type);
|
||||
return new GenericType(type);
|
||||
}
|
||||
|
||||
public static ArgType wildcard() {
|
||||
return new WildcardType(OBJECT, 0);
|
||||
}
|
||||
|
||||
public static ArgType wildcard(ArgType obj, int bound) {
|
||||
return new WildcardType(obj, bound);
|
||||
}
|
||||
|
||||
public static ArgType generic(String sign) {
|
||||
return parseSignature(sign);
|
||||
return new SignatureParser(sign).consumeType();
|
||||
}
|
||||
|
||||
public static ArgType generic(String obj, ArgType[] generics) {
|
||||
return new GenericObjectArg(obj, generics);
|
||||
return new GenericObject(obj, generics);
|
||||
}
|
||||
|
||||
public static ArgType genericInner(ArgType genericType, String innerName, ArgType[] generics) {
|
||||
return new GenericObject((GenericObject) genericType, innerName, generics);
|
||||
}
|
||||
|
||||
public static ArgType array(ArgType vtype) {
|
||||
@@ -82,14 +88,14 @@ public abstract class ArgType {
|
||||
return new UnknownArg(types);
|
||||
}
|
||||
|
||||
private abstract static class KnownTypeArg extends ArgType {
|
||||
private abstract static class KnownType extends ArgType {
|
||||
@Override
|
||||
public boolean isTypeKnown() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class PrimitiveArg extends KnownTypeArg {
|
||||
private static final class PrimitiveArg extends KnownType {
|
||||
private final PrimitiveType type;
|
||||
|
||||
public PrimitiveArg(PrimitiveType type) {
|
||||
@@ -118,10 +124,10 @@ public abstract class ArgType {
|
||||
}
|
||||
}
|
||||
|
||||
private static class ObjectArg extends KnownTypeArg {
|
||||
private static class ObjectType extends KnownType {
|
||||
private final String object;
|
||||
|
||||
public ObjectArg(String obj) {
|
||||
public ObjectType(String obj) {
|
||||
this.object = Utils.cleanObjectName(obj);
|
||||
this.hash = object.hashCode();
|
||||
}
|
||||
@@ -143,7 +149,7 @@ public abstract class ArgType {
|
||||
|
||||
@Override
|
||||
boolean internalEquals(Object obj) {
|
||||
return object.equals(((ObjectArg) obj).object);
|
||||
return object.equals(((ObjectType) obj).object);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -152,8 +158,8 @@ public abstract class ArgType {
|
||||
}
|
||||
}
|
||||
|
||||
private static final class GenericTypeArg extends ObjectArg {
|
||||
public GenericTypeArg(String obj) {
|
||||
private static final class GenericType extends ObjectType {
|
||||
public GenericType(String obj) {
|
||||
super(obj);
|
||||
}
|
||||
|
||||
@@ -163,24 +169,93 @@ public abstract class ArgType {
|
||||
}
|
||||
}
|
||||
|
||||
private static final class GenericObjectArg extends ObjectArg {
|
||||
private final ArgType[] generics;
|
||||
private static final class WildcardType extends ObjectType {
|
||||
private final ArgType type;
|
||||
private final int bounds;
|
||||
|
||||
public GenericObjectArg(String obj, ArgType[] generics) {
|
||||
public WildcardType(ArgType obj, int bound) {
|
||||
super(ArgType.OBJECT.getObject());
|
||||
this.type = obj;
|
||||
this.bounds = bound;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isGeneric() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArgType getWildcardType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return wildcard bounds:
|
||||
* <ul>
|
||||
* <li> 1 for upper bound (? extends A) </li>
|
||||
* <li> 0 no bounds (?) </li>
|
||||
* <li>-1 for lower bound (? super A) </li>
|
||||
* </ul>
|
||||
*/
|
||||
@Override
|
||||
public int getWildcardBounds() {
|
||||
return bounds;
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean internalEquals(Object obj) {
|
||||
return super.internalEquals(obj)
|
||||
&& bounds == ((WildcardType) obj).bounds
|
||||
&& type.equals(((WildcardType) obj).type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (bounds == 0) {
|
||||
return "?";
|
||||
}
|
||||
return "? " + (bounds == -1 ? "super" : "extends") + " " + type.toString();
|
||||
}
|
||||
}
|
||||
|
||||
private static class GenericObject extends ObjectType {
|
||||
private final ArgType[] generics;
|
||||
private final GenericObject outerType;
|
||||
|
||||
public GenericObject(String obj, ArgType[] generics) {
|
||||
super(obj);
|
||||
this.outerType = null;
|
||||
this.generics = generics;
|
||||
this.hash = obj.hashCode() + 31 * Arrays.hashCode(generics);
|
||||
}
|
||||
|
||||
public GenericObject(GenericObject outerType, String innerName, ArgType[] generics) {
|
||||
super(outerType.getObject() + "$" + innerName);
|
||||
this.outerType = outerType;
|
||||
this.generics = generics;
|
||||
this.hash = outerType.hashCode() + 31 * innerName.hashCode()
|
||||
+ 31 * 31 * Arrays.hashCode(generics);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isGeneric() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArgType[] getGenericTypes() {
|
||||
return generics;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArgType getOuterType() {
|
||||
return outerType;
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean internalEquals(Object obj) {
|
||||
return super.internalEquals(obj)
|
||||
&& Arrays.equals(generics, ((GenericObjectArg) obj).generics);
|
||||
&& Arrays.equals(generics, ((GenericObject) obj).generics);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -189,7 +264,7 @@ public abstract class ArgType {
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ArrayArg extends KnownTypeArg {
|
||||
private static final class ArrayArg extends KnownType {
|
||||
private final ArgType arrayElement;
|
||||
|
||||
public ArrayArg(ArgType arrayElement) {
|
||||
@@ -229,7 +304,7 @@ public abstract class ArgType {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return arrayElement.toString() + "[]";
|
||||
return arrayElement + "[]";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -299,13 +374,17 @@ public abstract class ArgType {
|
||||
}
|
||||
|
||||
public String getObject() {
|
||||
throw new UnsupportedOperationException();
|
||||
throw new UnsupportedOperationException("ArgType.getObject(), call class: " + this.getClass());
|
||||
}
|
||||
|
||||
public boolean isObject() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isGeneric() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isGenericType() {
|
||||
return false;
|
||||
}
|
||||
@@ -314,6 +393,21 @@ public abstract class ArgType {
|
||||
return null;
|
||||
}
|
||||
|
||||
public ArgType getWildcardType() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see jadx.core.dex.instructions.args.ArgType.WildcardType#getWildcardBounds()
|
||||
*/
|
||||
public int getWildcardBounds() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public ArgType getOuterType() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean isArray() {
|
||||
return false;
|
||||
}
|
||||
@@ -375,7 +469,7 @@ public abstract class ArgType {
|
||||
types.add(type);
|
||||
}
|
||||
}
|
||||
if (types.size() == 0) {
|
||||
if (types.isEmpty()) {
|
||||
return null;
|
||||
} else if (types.size() == 1) {
|
||||
PrimitiveType nt = types.get(0);
|
||||
@@ -400,7 +494,7 @@ public abstract class ArgType {
|
||||
String aObj = a.getObject();
|
||||
String bObj = b.getObject();
|
||||
if (aObj.equals(bObj)) {
|
||||
return (a.getGenericTypes() != null ? a : b);
|
||||
return a.getGenericTypes() != null ? a : b;
|
||||
} else if (aObj.equals(Consts.CLASS_OBJECT)) {
|
||||
return b;
|
||||
} else if (bObj.equals(Consts.CLASS_OBJECT)) {
|
||||
@@ -408,7 +502,7 @@ public abstract class ArgType {
|
||||
} else {
|
||||
// different objects
|
||||
String obj = clsp.getCommonAncestor(aObj, bObj);
|
||||
return (obj == null ? null : object(obj));
|
||||
return obj == null ? null : object(obj);
|
||||
}
|
||||
}
|
||||
if (a.isArray()) {
|
||||
@@ -419,7 +513,7 @@ public abstract class ArgType {
|
||||
return OBJECT;
|
||||
} else {
|
||||
ArgType res = merge(ea, eb);
|
||||
return (res == null ? null : ArgType.array(res));
|
||||
return res == null ? null : ArgType.array(res);
|
||||
}
|
||||
} else if (b.equals(OBJECT)) {
|
||||
return OBJECT;
|
||||
@@ -459,148 +553,7 @@ public abstract class ArgType {
|
||||
}
|
||||
}
|
||||
|
||||
public static ArgType parseSignature(String sign) {
|
||||
int b = sign.indexOf('<');
|
||||
if (b == -1) {
|
||||
return parse(sign);
|
||||
}
|
||||
if (sign.charAt(0) == '[') {
|
||||
return array(parseSignature(sign.substring(1)));
|
||||
}
|
||||
String obj = sign.substring(0, b) + ";";
|
||||
String genericsStr = sign.substring(b + 1, sign.length() - 2);
|
||||
List<ArgType> generics = parseSignatureList(genericsStr);
|
||||
if (generics != null) {
|
||||
return generic(obj, generics.toArray(new ArgType[generics.size()]));
|
||||
} else {
|
||||
return object(obj);
|
||||
}
|
||||
}
|
||||
|
||||
public static List<ArgType> parseSignatureList(String str) {
|
||||
try {
|
||||
return parseSignatureListInner(str, true);
|
||||
} catch (Throwable e) {
|
||||
LOG.warn("Signature parse exception: {}", str, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static List<ArgType> parseSignatureListInner(String str, boolean parsePrimitives) {
|
||||
if (str.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
if (str.equals("*")) {
|
||||
return Arrays.asList(UNKNOWN);
|
||||
}
|
||||
List<ArgType> signs = new ArrayList<ArgType>(3);
|
||||
int obj = 0;
|
||||
int objStart = 0;
|
||||
int gen = 0;
|
||||
int arr = 0;
|
||||
|
||||
int pos = 0;
|
||||
ArgType type = null;
|
||||
while (pos < str.length()) {
|
||||
char c = str.charAt(pos);
|
||||
switch (c) {
|
||||
case 'L':
|
||||
case 'T':
|
||||
if (obj == 0 && gen == 0) {
|
||||
obj++;
|
||||
objStart = pos;
|
||||
}
|
||||
break;
|
||||
|
||||
case ';':
|
||||
if (obj == 1 && gen == 0) {
|
||||
obj--;
|
||||
String o = str.substring(objStart, pos + 1);
|
||||
type = parseSignature(o);
|
||||
}
|
||||
break;
|
||||
|
||||
case ':': // generic types map separator
|
||||
if (gen == 0) {
|
||||
obj = 0;
|
||||
String o = str.substring(objStart, pos);
|
||||
if (o.length() > 0) {
|
||||
type = genericType(o);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case '<':
|
||||
gen++;
|
||||
break;
|
||||
case '>':
|
||||
gen--;
|
||||
break;
|
||||
|
||||
case '[':
|
||||
if (obj == 0 && gen == 0) {
|
||||
arr++;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
if (parsePrimitives && obj == 0 && gen == 0) {
|
||||
type = parse(c);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (type != null) {
|
||||
if (arr == 0) {
|
||||
signs.add(type);
|
||||
} else {
|
||||
for (int i = 0; i < arr; i++) {
|
||||
type = array(type);
|
||||
}
|
||||
signs.add(type);
|
||||
arr = 0;
|
||||
}
|
||||
type = null;
|
||||
objStart = pos + 1;
|
||||
}
|
||||
pos++;
|
||||
}
|
||||
return signs;
|
||||
}
|
||||
|
||||
public static Map<ArgType, List<ArgType>> parseGenericMap(String gen) {
|
||||
try {
|
||||
Map<ArgType, List<ArgType>> genericMap = null;
|
||||
List<ArgType> genTypes = parseSignatureListInner(gen, false);
|
||||
if (genTypes != null) {
|
||||
genericMap = new LinkedHashMap<ArgType, List<ArgType>>(2);
|
||||
ArgType prev = null;
|
||||
List<ArgType> genList = new ArrayList<ArgType>(2);
|
||||
for (ArgType arg : genTypes) {
|
||||
if (arg.isGenericType()) {
|
||||
if (prev != null) {
|
||||
genericMap.put(prev, genList);
|
||||
genList = new ArrayList<ArgType>();
|
||||
}
|
||||
prev = arg;
|
||||
} else {
|
||||
if (!arg.getObject().equals(Consts.CLASS_OBJECT)) {
|
||||
genList.add(arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (prev != null) {
|
||||
genericMap.put(prev, genList);
|
||||
}
|
||||
}
|
||||
return genericMap;
|
||||
} catch (Throwable e) {
|
||||
LOG.warn("Generic map parse exception: {}", gen, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static ArgType parse(char f) {
|
||||
public static ArgType parse(char f) {
|
||||
switch (f) {
|
||||
case 'Z':
|
||||
return BOOLEAN;
|
||||
|
||||
@@ -3,6 +3,9 @@ package jadx.core.dex.instructions.args;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.utils.InsnUtils;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.android.dx.io.instructions.DecodedInstruction;
|
||||
|
||||
/**
|
||||
@@ -11,6 +14,8 @@ import com.android.dx.io.instructions.DecodedInstruction;
|
||||
*/
|
||||
public abstract class InsnArg extends Typed {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(InsnArg.class);
|
||||
|
||||
protected InsnNode parentInsn;
|
||||
|
||||
public static RegisterArg reg(int regNum, ArgType type) {
|
||||
@@ -69,7 +74,13 @@ public abstract class InsnArg extends Typed {
|
||||
|
||||
public InsnArg wrapInstruction(InsnNode insn) {
|
||||
InsnNode parent = parentInsn;
|
||||
assert parent != insn : "Can't wrap instruction info itself";
|
||||
if (parent == null) {
|
||||
return null;
|
||||
}
|
||||
if (parent == insn) {
|
||||
LOG.debug("Can't wrap instruction info itself: " + insn);
|
||||
return null;
|
||||
}
|
||||
int count = parent.getArgsCount();
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (parent.getArg(i) == this) {
|
||||
|
||||
@@ -7,8 +7,8 @@ public final class InsnWrapArg extends InsnArg {
|
||||
private final InsnNode wrappedInsn;
|
||||
|
||||
public InsnWrapArg(InsnNode insn) {
|
||||
ArgType type = (insn.getResult() == null ? ArgType.VOID : insn.getResult().getType());
|
||||
this.typedVar = new TypedVar(type);
|
||||
RegisterArg result = insn.getResult();
|
||||
this.typedVar = new TypedVar((result != null ? result.getType() : ArgType.VOID));
|
||||
this.wrappedInsn = insn;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,9 @@ import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
public final class LiteralArg extends InsnArg {
|
||||
|
||||
public static final LiteralArg TRUE = new LiteralArg(1, ArgType.BOOLEAN);
|
||||
public static final LiteralArg FALSE = new LiteralArg(0, ArgType.BOOLEAN);
|
||||
|
||||
private final long literal;
|
||||
|
||||
public LiteralArg(long value, ArgType type) {
|
||||
@@ -42,10 +45,31 @@ public final class LiteralArg extends InsnArg {
|
||||
|| type == PrimitiveType.LONG);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return (int) (literal ^ (literal >>> 32)) + 31 * getType().hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
LiteralArg that = (LiteralArg) o;
|
||||
return literal == that.literal && getType().equals(that.getType());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
try {
|
||||
return "(" + TypeGen.literalToString(literal, getType()) + " " + typedVar + ")";
|
||||
String value = TypeGen.literalToString(literal, getType());
|
||||
if (getType().equals(ArgType.BOOLEAN) && (value.equals("true") || value.equals("false"))) {
|
||||
return value;
|
||||
}
|
||||
return "(" + value + " " + typedVar + ")";
|
||||
} catch (JadxRuntimeException ex) {
|
||||
// can't convert literal to string
|
||||
return "(" + literal + " " + typedVar + ")";
|
||||
|
||||
@@ -30,17 +30,19 @@ public enum PrimitiveType {
|
||||
}
|
||||
|
||||
public static PrimitiveType getWidest(PrimitiveType a, PrimitiveType b) {
|
||||
if (a.ordinal() > b.ordinal())
|
||||
if (a.ordinal() > b.ordinal()) {
|
||||
return a;
|
||||
else
|
||||
} else {
|
||||
return b;
|
||||
}
|
||||
}
|
||||
|
||||
public static PrimitiveType getSmaller(PrimitiveType a, PrimitiveType b) {
|
||||
if (a.ordinal() < b.ordinal())
|
||||
if (a.ordinal() < b.ordinal()) {
|
||||
return a;
|
||||
else
|
||||
} else {
|
||||
return b;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -40,12 +40,13 @@ public class RegisterArg extends InsnArg {
|
||||
public InsnNode getAssignInsn() {
|
||||
for (InsnArg arg : getTypedVar().getUseList()) {
|
||||
InsnNode assignInsn = arg.getParentInsn();
|
||||
if (assignInsn == null)
|
||||
if (assignInsn == null) {
|
||||
// assign as function argument
|
||||
return null;
|
||||
else if (assignInsn.getResult() != null
|
||||
&& assignInsn.getResult().getRegNum() == regNum)
|
||||
} else if (assignInsn.getResult() != null
|
||||
&& assignInsn.getResult().getRegNum() == regNum) {
|
||||
return assignInsn;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -87,7 +88,7 @@ public class RegisterArg extends InsnArg {
|
||||
public boolean isThis() {
|
||||
if (isRegister()) {
|
||||
String name = getTypedVar().getName();
|
||||
if (name != null && name.equals("this")) {
|
||||
if ("this".equals(name)) {
|
||||
return true;
|
||||
}
|
||||
// maybe it was moved from 'this' register
|
||||
@@ -109,13 +110,17 @@ public class RegisterArg extends InsnArg {
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (obj == null) return false;
|
||||
if (getClass() != obj.getClass()) return false;
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
RegisterArg other = (RegisterArg) obj;
|
||||
if (regNum != other.regNum) return false;
|
||||
if (!typedVar.equals(other.typedVar)) return false;
|
||||
return true;
|
||||
return regNum == other.regNum && typedVar.equals(other.typedVar);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package jadx.core.dex.instructions.args;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
public class TypedVar {
|
||||
@@ -38,10 +39,6 @@ public class TypedVar {
|
||||
}
|
||||
}
|
||||
|
||||
public List<InsnArg> getUseList() {
|
||||
return useList;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
@@ -50,10 +47,24 @@ public class TypedVar {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public List<InsnArg> getUseList() {
|
||||
return useList;
|
||||
}
|
||||
|
||||
public void removeUse(InsnArg arg) {
|
||||
Iterator<InsnArg> it = useList.iterator();
|
||||
while (it.hasNext()) {
|
||||
InsnArg use = it.next();
|
||||
if (use == arg) {
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void mergeName(TypedVar arg) {
|
||||
String name = arg.getName();
|
||||
if (name != null) {
|
||||
setName(name);
|
||||
String argName = arg.getName();
|
||||
if (argName != null) {
|
||||
setName(argName);
|
||||
} else if (getName() != null) {
|
||||
arg.setName(getName());
|
||||
}
|
||||
@@ -70,13 +81,23 @@ public class TypedVar {
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (obj == null) return false;
|
||||
if (!(obj instanceof TypedVar)) return false;
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (!(obj instanceof TypedVar)) {
|
||||
return false;
|
||||
}
|
||||
TypedVar other = (TypedVar) obj;
|
||||
if (!type.equals(other.type)) return false;
|
||||
if (!type.equals(other.type)) {
|
||||
return false;
|
||||
}
|
||||
if (name == null) {
|
||||
if (other.name != null) return false;
|
||||
if (other.name != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!name.equals(other.name)) {
|
||||
return false;
|
||||
}
|
||||
@@ -86,8 +107,9 @@ public class TypedVar {
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (name != null)
|
||||
if (name != null) {
|
||||
sb.append('\'').append(name).append("' ");
|
||||
}
|
||||
sb.append(type);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package jadx.core.dex.instructions.mods;
|
||||
|
||||
import jadx.core.dex.instructions.InsnType;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.LiteralArg;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.regions.IfCondition;
|
||||
import jadx.core.utils.InsnUtils;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
public class TernaryInsn extends InsnNode {
|
||||
|
||||
private final IfCondition condition;
|
||||
|
||||
public TernaryInsn(IfCondition condition, RegisterArg result, InsnArg th, InsnArg els) {
|
||||
super(InsnType.TERNARY, 2);
|
||||
setResult(result);
|
||||
|
||||
if (th.equals(LiteralArg.FALSE) && els.equals(LiteralArg.TRUE)) {
|
||||
// inverted
|
||||
this.condition = IfCondition.invert(condition);
|
||||
addArg(els);
|
||||
addArg(th);
|
||||
} else {
|
||||
this.condition = condition;
|
||||
addArg(th);
|
||||
addArg(els);
|
||||
}
|
||||
}
|
||||
|
||||
public IfCondition getCondition() {
|
||||
return condition;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return InsnUtils.formatOffset(offset) + ": TERNARY"
|
||||
+ getResult() + " = "
|
||||
+ Utils.listToString(getArguments());
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package jadx.core.dex.nodes;
|
||||
|
||||
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;
|
||||
@@ -21,8 +22,13 @@ public class BlockNode extends AttrNode implements IBlock {
|
||||
private List<BlockNode> successors = new ArrayList<BlockNode>(1);
|
||||
private List<BlockNode> cleanSuccessors;
|
||||
|
||||
private BitSet doms; // all dominators
|
||||
private BlockNode idom; // immediate dominator
|
||||
// all dominators
|
||||
private BitSet doms;
|
||||
// dominance frontier
|
||||
private BitSet domFrontier;
|
||||
// immediate dominator
|
||||
private BlockNode idom;
|
||||
// blocks on which dominates this block
|
||||
private final List<BlockNode> dominatesOn = new ArrayList<BlockNode>(1);
|
||||
|
||||
private BlockRegState startState;
|
||||
@@ -72,20 +78,22 @@ public class BlockNode extends AttrNode implements IBlock {
|
||||
LoopAttr loop = (LoopAttr) block.getAttributes().get(AttributeType.LOOP);
|
||||
if (loop == null) {
|
||||
for (BlockNode b : sucList) {
|
||||
if (!b.getAttributes().contains(AttributeType.EXC_HANDLER))
|
||||
if (!b.getAttributes().contains(AttributeType.EXC_HANDLER)) {
|
||||
nodes.add(b);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (BlockNode b : sucList) {
|
||||
if (!b.getAttributes().contains(AttributeType.EXC_HANDLER)) {
|
||||
// don't follow back edge
|
||||
if (loop.getStart() == b && loop.getEnd() == block)
|
||||
if (loop.getStart() == b && loop.getEnd() == block) {
|
||||
continue;
|
||||
}
|
||||
nodes.add(b);
|
||||
}
|
||||
}
|
||||
}
|
||||
return (nodes.size() == sucList.size() ? sucList : nodes);
|
||||
return nodes.size() == sucList.size() ? sucList : nodes;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -115,6 +123,14 @@ public class BlockNode extends AttrNode implements IBlock {
|
||||
this.doms = doms;
|
||||
}
|
||||
|
||||
public BitSet getDomFrontier() {
|
||||
return domFrontier;
|
||||
}
|
||||
|
||||
public void setDomFrontier(BitSet domFrontier) {
|
||||
this.domFrontier = domFrontier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Immediate dominator
|
||||
*/
|
||||
@@ -146,6 +162,14 @@ public class BlockNode extends AttrNode implements IBlock {
|
||||
this.endState = endState;
|
||||
}
|
||||
|
||||
public boolean isSynthetic() {
|
||||
return getAttributes().contains(AttributeFlag.SYNTHETIC);
|
||||
}
|
||||
|
||||
public boolean isReturnBlock() {
|
||||
return getAttributes().contains(AttributeFlag.RETURN);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return id; // TODO id can change during reindex
|
||||
@@ -153,13 +177,25 @@ public class BlockNode extends AttrNode implements IBlock {
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (obj == null) return false;
|
||||
if (hashCode() != obj.hashCode()) return false;
|
||||
if (!(obj instanceof BlockNode)) return false;
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (hashCode() != obj.hashCode()) {
|
||||
return false;
|
||||
}
|
||||
if (!(obj instanceof BlockNode)) {
|
||||
return false;
|
||||
}
|
||||
BlockNode other = (BlockNode) obj;
|
||||
if (id != other.id) return false;
|
||||
if (startOffset != other.startOffset) return false;
|
||||
if (id != other.id) {
|
||||
return false;
|
||||
}
|
||||
if (startOffset != other.startOffset) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,13 +16,14 @@ import jadx.core.dex.instructions.args.LiteralArg;
|
||||
import jadx.core.dex.instructions.args.PrimitiveType;
|
||||
import jadx.core.dex.nodes.parser.AnnotationsParser;
|
||||
import jadx.core.dex.nodes.parser.FieldValueAttr;
|
||||
import jadx.core.dex.nodes.parser.SignatureParser;
|
||||
import jadx.core.dex.nodes.parser.StaticValuesParser;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.DecodeException;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -39,49 +40,59 @@ public class ClassNode extends LineAttrNode implements ILoadable {
|
||||
|
||||
private final DexNode dex;
|
||||
private final ClassInfo clsInfo;
|
||||
private final AccessInfo accessFlags;
|
||||
private ClassInfo superClass;
|
||||
private List<ClassInfo> interfaces;
|
||||
private Map<ArgType, List<ArgType>> genericMap;
|
||||
|
||||
private final List<MethodNode> methods = new ArrayList<MethodNode>();
|
||||
private final List<FieldNode> fields = new ArrayList<FieldNode>();
|
||||
|
||||
private final AccessInfo accessFlags;
|
||||
private final List<MethodNode> methods;
|
||||
private final List<FieldNode> fields;
|
||||
private Map<Object, FieldNode> constFields = Collections.emptyMap();
|
||||
private List<ClassNode> innerClasses = Collections.emptyList();
|
||||
|
||||
private final Map<Object, FieldNode> constFields = new HashMap<Object, FieldNode>();
|
||||
|
||||
private CodeWriter code; // generated code
|
||||
// store decompiled code
|
||||
private CodeWriter code;
|
||||
// store parent for inner classes or 'this' otherwise
|
||||
private ClassNode parentClass;
|
||||
|
||||
public ClassNode(DexNode dex, ClassDef cls) throws DecodeException {
|
||||
this.dex = dex;
|
||||
this.clsInfo = ClassInfo.fromDex(dex, cls.getTypeIndex());
|
||||
try {
|
||||
this.superClass = cls.getSupertypeIndex() == DexNode.NO_INDEX
|
||||
? null
|
||||
: ClassInfo.fromDex(dex, cls.getSupertypeIndex());
|
||||
|
||||
if (cls.getSupertypeIndex() == DexNode.NO_INDEX) {
|
||||
this.superClass = null;
|
||||
} else {
|
||||
this.superClass = ClassInfo.fromDex(dex, cls.getSupertypeIndex());
|
||||
}
|
||||
this.interfaces = new ArrayList<ClassInfo>(cls.getInterfaces().length);
|
||||
for (short interfaceIdx : cls.getInterfaces()) {
|
||||
this.interfaces.add(ClassInfo.fromDex(dex, interfaceIdx));
|
||||
}
|
||||
|
||||
if (cls.getClassDataOffset() != 0) {
|
||||
ClassData clsData = dex.readClassData(cls);
|
||||
int mthsCount = clsData.getDirectMethods().length + clsData.getVirtualMethods().length;
|
||||
int fieldsCount = clsData.getStaticFields().length + clsData.getInstanceFields().length;
|
||||
|
||||
for (Method mth : clsData.getDirectMethods())
|
||||
methods = new ArrayList<MethodNode>(mthsCount);
|
||||
fields = new ArrayList<FieldNode>(fieldsCount);
|
||||
|
||||
for (Method mth : clsData.getDirectMethods()) {
|
||||
methods.add(new MethodNode(this, mth));
|
||||
|
||||
for (Method mth : clsData.getVirtualMethods())
|
||||
}
|
||||
for (Method mth : clsData.getVirtualMethods()) {
|
||||
methods.add(new MethodNode(this, mth));
|
||||
}
|
||||
|
||||
for (Field f : clsData.getStaticFields())
|
||||
for (Field f : clsData.getStaticFields()) {
|
||||
fields.add(new FieldNode(this, f));
|
||||
|
||||
}
|
||||
loadStaticValues(cls, fields);
|
||||
|
||||
for (Field f : clsData.getInstanceFields())
|
||||
for (Field f : clsData.getInstanceFields()) {
|
||||
fields.add(new FieldNode(this, f));
|
||||
}
|
||||
} else {
|
||||
methods = Collections.emptyList();
|
||||
fields = Collections.emptyList();
|
||||
}
|
||||
|
||||
loadAnnotations(cls);
|
||||
@@ -98,13 +109,14 @@ public class ClassNode extends LineAttrNode implements ILoadable {
|
||||
}
|
||||
}
|
||||
|
||||
// restore original access flags from dalvik annotation if present
|
||||
int accFlagsValue;
|
||||
Annotation a = getAttributes().getAnnotation(Consts.DALVIK_INNER_CLASS);
|
||||
if (a != null)
|
||||
if (a != null) {
|
||||
accFlagsValue = (Integer) a.getValues().get("accessFlags");
|
||||
else
|
||||
} else {
|
||||
accFlagsValue = cls.getAccessFlags();
|
||||
|
||||
}
|
||||
this.accessFlags = new AccessInfo(accFlagsValue, AFType.CLASS);
|
||||
|
||||
} catch (Exception e) {
|
||||
@@ -116,7 +128,7 @@ public class ClassNode extends LineAttrNode implements ILoadable {
|
||||
int offset = cls.getAnnotationsOffset();
|
||||
if (offset != 0) {
|
||||
try {
|
||||
new AnnotationsParser(this, offset);
|
||||
new AnnotationsParser(this).parse(offset);
|
||||
} catch (DecodeException e) {
|
||||
LOG.error("Error parsing annotations in " + this, e);
|
||||
}
|
||||
@@ -134,8 +146,8 @@ public class ClassNode extends LineAttrNode implements ILoadable {
|
||||
int offset = cls.getStaticValuesOffset();
|
||||
if (offset != 0) {
|
||||
StaticValuesParser parser = new StaticValuesParser(dex, dex.openSection(offset));
|
||||
parser.processFields(staticFields);
|
||||
|
||||
int count = parser.processFields(staticFields);
|
||||
constFields = new LinkedHashMap<Object, FieldNode>(count);
|
||||
for (FieldNode f : staticFields) {
|
||||
AccessInfo accFlags = f.getAccessFlags();
|
||||
if (accFlags.isStatic() && accFlags.isFinal()) {
|
||||
@@ -151,50 +163,43 @@ public class ClassNode extends LineAttrNode implements ILoadable {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void parseClassSignature() {
|
||||
Annotation a = this.getAttributes().getAnnotation(Consts.DALVIK_SIGNATURE);
|
||||
if (a == null)
|
||||
SignatureParser sp = SignatureParser.fromNode(this);
|
||||
if (sp == null) {
|
||||
return;
|
||||
|
||||
String sign = Utils.mergeSignature((List<String>) a.getDefaultValue());
|
||||
// parse generic map
|
||||
int end = Utils.getGenericEnd(sign);
|
||||
if (end != -1) {
|
||||
String gen = sign.substring(1, end);
|
||||
genericMap = ArgType.parseGenericMap(gen);
|
||||
sign = sign.substring(end + 1);
|
||||
}
|
||||
|
||||
// parse super class signature and interfaces
|
||||
List<ArgType> list = ArgType.parseSignatureList(sign);
|
||||
if (list != null && !list.isEmpty()) {
|
||||
try {
|
||||
ArgType st = list.remove(0);
|
||||
this.superClass = ClassInfo.fromType(st);
|
||||
int i = 0;
|
||||
for (ArgType it : list) {
|
||||
ClassInfo interf = ClassInfo.fromType(it);
|
||||
interfaces.set(i, interf);
|
||||
i++;
|
||||
try {
|
||||
// parse class generic map
|
||||
genericMap = sp.consumeGenericMap();
|
||||
// parse super class signature
|
||||
superClass = ClassInfo.fromType(sp.consumeType());
|
||||
// parse interfaces signatures
|
||||
for (int i = 0; i < interfaces.size(); i++) {
|
||||
ArgType type = sp.consumeType();
|
||||
if (type != null) {
|
||||
interfaces.set(i, ClassInfo.fromType(type));
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
LOG.warn("Can't set signatures for class: {}, sign: {}", this, sign, e);
|
||||
}
|
||||
} catch (JadxRuntimeException e) {
|
||||
LOG.error("Class signature parse error: " + this, e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void setFieldsTypesFromSignature() {
|
||||
for (FieldNode field : fields) {
|
||||
Annotation a = field.getAttributes().getAnnotation(Consts.DALVIK_SIGNATURE);
|
||||
if (a == null)
|
||||
continue;
|
||||
|
||||
String sign = Utils.mergeSignature((List<String>) a.getDefaultValue());
|
||||
ArgType gType = ArgType.parseSignature(sign);
|
||||
if (gType != null)
|
||||
field.setType(gType);
|
||||
SignatureParser sp = SignatureParser.fromNode(field);
|
||||
if (sp != null) {
|
||||
try {
|
||||
ArgType gType = sp.consumeType();
|
||||
if (gType != null) {
|
||||
field.setType(gType);
|
||||
}
|
||||
} catch (JadxRuntimeException e) {
|
||||
LOG.error("Field signature parse error: " + field, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -288,33 +293,40 @@ public class ClassNode extends LineAttrNode implements ILoadable {
|
||||
public FieldNode searchFieldById(int id) {
|
||||
String name = FieldInfo.getNameById(dex, id);
|
||||
for (FieldNode f : fields) {
|
||||
if (f.getName().equals(name))
|
||||
if (f.getName().equals(name)) {
|
||||
return f;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public FieldNode searchField(FieldInfo field) {
|
||||
String name = field.getName();
|
||||
return searchFieldByName(field.getName());
|
||||
}
|
||||
|
||||
public FieldNode searchFieldByName(String name) {
|
||||
for (FieldNode f : fields) {
|
||||
if (f.getName().equals(name))
|
||||
if (f.getName().equals(name)) {
|
||||
return f;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public MethodNode searchMethod(MethodInfo mth) {
|
||||
for (MethodNode m : methods) {
|
||||
if (m.getMethodInfo().equals(mth))
|
||||
if (m.getMethodInfo().equals(mth)) {
|
||||
return m;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public MethodNode searchMethodByName(String shortId) {
|
||||
for (MethodNode m : methods) {
|
||||
if (m.getMethodInfo().getShortId().equals(shortId))
|
||||
if (m.getMethodInfo().getShortId().equals(shortId)) {
|
||||
return m;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -323,13 +335,27 @@ public class ClassNode extends LineAttrNode implements ILoadable {
|
||||
return searchMethodByName(MethodInfo.fromDex(dex, id).getShortId());
|
||||
}
|
||||
|
||||
public ClassNode getParentClass() {
|
||||
if (parentClass == null) {
|
||||
if (clsInfo.isInner()) {
|
||||
ClassNode parent = dex().resolveClass(clsInfo.getParentClass());
|
||||
parent = parent == null ? this : parent;
|
||||
parentClass = parent;
|
||||
} else {
|
||||
parentClass = this;
|
||||
}
|
||||
}
|
||||
return parentClass;
|
||||
}
|
||||
|
||||
public List<ClassNode> getInnerClasses() {
|
||||
return innerClasses;
|
||||
}
|
||||
|
||||
public void addInnerClass(ClassNode cls) {
|
||||
if (innerClasses.isEmpty())
|
||||
if (innerClasses.isEmpty()) {
|
||||
innerClasses = new ArrayList<ClassNode>(3);
|
||||
}
|
||||
innerClasses.add(cls);
|
||||
}
|
||||
|
||||
@@ -348,7 +374,7 @@ public class ClassNode extends LineAttrNode implements ILoadable {
|
||||
if (mth.getAccessFlags().isConstructor()
|
||||
&& mth.getMethodInfo().isConstructor()
|
||||
&& (mth.getMethodInfo().getArgsCount() == 0
|
||||
|| (mth.getArguments(false) != null && mth.getArguments(false).isEmpty()))) {
|
||||
|| (mth.getArguments(false) != null && mth.getArguments(false).isEmpty()))) {
|
||||
return mth;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package jadx.core.dex.nodes;
|
||||
|
||||
public class Edge {
|
||||
private final BlockNode source;
|
||||
private final BlockNode target;
|
||||
|
||||
public Edge(BlockNode source, BlockNode target) {
|
||||
this.source = source;
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
public BlockNode getSource() {
|
||||
return source;
|
||||
}
|
||||
|
||||
public BlockNode getTarget() {
|
||||
return target;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
Edge edge = (Edge) o;
|
||||
return source.equals(edge.source) && target.equals(edge.target);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return source.hashCode() + 31 * target.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Edge: " + source + " -> " + target;
|
||||
}
|
||||
}
|
||||
@@ -10,12 +10,14 @@ import com.android.dx.io.ClassData.Field;
|
||||
|
||||
public class FieldNode extends LineAttrNode {
|
||||
|
||||
private final ClassNode parent;
|
||||
private final FieldInfo fieldInfo;
|
||||
private final AccessInfo accFlags;
|
||||
|
||||
private ArgType type; // store signature
|
||||
|
||||
public FieldNode(ClassNode cls, Field field) {
|
||||
this.parent = cls;
|
||||
this.fieldInfo = FieldInfo.fromDex(cls.dex(), field.getFieldIndex());
|
||||
this.type = fieldInfo.getType();
|
||||
this.accFlags = new AccessInfo(field.getAccessFlags(), AFType.FIELD);
|
||||
@@ -41,6 +43,10 @@ public class FieldNode extends LineAttrNode {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public ClassNode getParentClass() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return fieldInfo.hashCode();
|
||||
@@ -48,14 +54,18 @@ public class FieldNode extends LineAttrNode {
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (obj == null || getClass() != obj.getClass()) return false;
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null || getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
FieldNode other = (FieldNode) obj;
|
||||
return fieldInfo.equals(other.fieldInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return fieldInfo.getDeclClass() + "." + fieldInfo.getName() + " " + type;
|
||||
return fieldInfo.getDeclClass() + "." + fieldInfo.getName() + " :" + type;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,4 +8,5 @@ public interface IRegion extends IContainer {
|
||||
|
||||
List<IContainer> getSubBlocks();
|
||||
|
||||
boolean replaceSubBlock(IContainer oldBlock, IContainer newBlock);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import java.util.List;
|
||||
|
||||
public class InsnContainer extends AttrNode implements IBlock {
|
||||
|
||||
private List<InsnNode> insns;
|
||||
private final List<InsnNode> insns;
|
||||
|
||||
public InsnContainer(List<InsnNode> insns) {
|
||||
this.insns = insns;
|
||||
|
||||
@@ -24,23 +24,21 @@ public class InsnNode extends LineAttrNode {
|
||||
protected int offset;
|
||||
protected int insnHashCode = super.hashCode();
|
||||
|
||||
protected InsnNode(InsnType type) {
|
||||
this(type, 1);
|
||||
}
|
||||
|
||||
public InsnNode(InsnType type, int argsCount) {
|
||||
this.insnType = type;
|
||||
this.offset = -1;
|
||||
|
||||
if (argsCount == 0)
|
||||
if (argsCount == 0) {
|
||||
this.arguments = Collections.emptyList();
|
||||
else
|
||||
} else {
|
||||
this.arguments = new ArrayList<InsnArg>(argsCount);
|
||||
}
|
||||
}
|
||||
|
||||
public void setResult(RegisterArg res) {
|
||||
if (res != null)
|
||||
if (res != null) {
|
||||
res.setParentInsn(this);
|
||||
}
|
||||
this.result = res;
|
||||
}
|
||||
|
||||
@@ -71,8 +69,9 @@ public class InsnNode extends LineAttrNode {
|
||||
|
||||
public boolean containsArg(RegisterArg arg) {
|
||||
for (InsnArg a : arguments) {
|
||||
if (a == arg || (a.isRegister() && ((RegisterArg) a).getRegNum() == arg.getRegNum()))
|
||||
if (a == arg || (a.isRegister() && ((RegisterArg) a).getRegNum() == arg.getRegNum())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -88,12 +87,11 @@ public class InsnNode extends LineAttrNode {
|
||||
InsnArg arg = arguments.get(i);
|
||||
if (arg == from) {
|
||||
// TODO correct remove from use list
|
||||
// from.getTypedVar().getUseList().remove(from);
|
||||
setArg(i, to);
|
||||
return true;
|
||||
} else if (arg.isInsnWrap()) {
|
||||
if (((InsnWrapArg) arg).getWrapInsn().replaceArg(from, to))
|
||||
return true;
|
||||
}
|
||||
if (arg.isInsnWrap() && ((InsnWrapArg) arg).getWrapInsn().replaceArg(from, to)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
@@ -153,8 +151,10 @@ public class InsnNode extends LineAttrNode {
|
||||
case STR_CONCAT:
|
||||
case MOVE_EXCEPTION:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -176,14 +176,25 @@ public class InsnNode extends LineAttrNode {
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (obj == null) return false;
|
||||
if (hashCode() != obj.hashCode()) return false;
|
||||
if (!(obj instanceof InsnNode)) return false;
|
||||
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (hashCode() != obj.hashCode()) {
|
||||
return false;
|
||||
}
|
||||
if (!(obj instanceof InsnNode)) {
|
||||
return false;
|
||||
}
|
||||
InsnNode other = (InsnNode) obj;
|
||||
if (insnType != other.insnType) return false;
|
||||
if (arguments.size() != other.arguments.size()) return false;
|
||||
if (insnType != other.insnType) {
|
||||
return false;
|
||||
}
|
||||
if (arguments.size() != other.arguments.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO !!! finish equals
|
||||
return true;
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
package jadx.core.dex.nodes;
|
||||
|
||||
import jadx.core.Consts;
|
||||
import jadx.core.dex.attributes.AttributeFlag;
|
||||
import jadx.core.dex.attributes.JumpAttribute;
|
||||
import jadx.core.dex.attributes.LineAttrNode;
|
||||
import jadx.core.dex.attributes.LoopAttr;
|
||||
import jadx.core.dex.attributes.annotations.Annotation;
|
||||
import jadx.core.dex.info.AccessInfo;
|
||||
import jadx.core.dex.info.AccessInfo.AFType;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
@@ -18,11 +16,14 @@ import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.nodes.parser.DebugInfoParser;
|
||||
import jadx.core.dex.nodes.parser.SignatureParser;
|
||||
import jadx.core.dex.regions.Region;
|
||||
import jadx.core.dex.trycatch.ExcHandlerAttr;
|
||||
import jadx.core.dex.trycatch.ExceptionHandler;
|
||||
import jadx.core.dex.trycatch.TryCatchBlock;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.DecodeException;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
@@ -60,8 +61,8 @@ public class MethodNode extends LineAttrNode implements ILoadable {
|
||||
private BlockNode enterBlock;
|
||||
private List<BlockNode> exitBlocks;
|
||||
|
||||
private IContainer region;
|
||||
private List<ExceptionHandler> exceptionHandlers;
|
||||
private Region region;
|
||||
private List<ExceptionHandler> exceptionHandlers = Collections.emptyList();
|
||||
private List<LoopAttr> loops = Collections.emptyList();
|
||||
|
||||
public MethodNode(ClassNode classNode, Method mthData) {
|
||||
@@ -90,8 +91,9 @@ public class MethodNode extends LineAttrNode implements ILoadable {
|
||||
InsnNode[] insnByOffset = decoder.run();
|
||||
instructions = new ArrayList<InsnNode>();
|
||||
for (InsnNode insn : insnByOffset) {
|
||||
if (insn != null)
|
||||
if (insn != null) {
|
||||
instructions.add(insn);
|
||||
}
|
||||
}
|
||||
((ArrayList<InsnNode>) instructions).trimToSize();
|
||||
|
||||
@@ -124,71 +126,54 @@ public class MethodNode extends LineAttrNode implements ILoadable {
|
||||
|
||||
@Override
|
||||
public void unload() {
|
||||
if (noCode)
|
||||
if (noCode) {
|
||||
return;
|
||||
|
||||
if (instructions != null) instructions.clear();
|
||||
}
|
||||
if (instructions != null) {
|
||||
instructions.clear();
|
||||
}
|
||||
blocks = null;
|
||||
exitBlocks = null;
|
||||
if (exceptionHandlers != null) exceptionHandlers.clear();
|
||||
getAttributes().clear();
|
||||
exceptionHandlers.clear();
|
||||
noCode = true;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private boolean parseSignature() {
|
||||
Annotation a = getAttributes().getAnnotation(Consts.DALVIK_SIGNATURE);
|
||||
if (a == null)
|
||||
return false;
|
||||
|
||||
String sign = Utils.mergeSignature((List<String>) a.getDefaultValue());
|
||||
|
||||
// parse generic map
|
||||
int end = Utils.getGenericEnd(sign);
|
||||
if (end != -1) {
|
||||
String gen = sign.substring(1, end);
|
||||
genericMap = ArgType.parseGenericMap(gen);
|
||||
sign = sign.substring(end + 1);
|
||||
}
|
||||
|
||||
int firstBracket = sign.indexOf('(');
|
||||
int lastBracket = sign.lastIndexOf(')');
|
||||
String argsTypesStr = sign.substring(firstBracket + 1, lastBracket);
|
||||
String returnType = sign.substring(lastBracket + 1);
|
||||
|
||||
retType = ArgType.parseSignature(returnType);
|
||||
if (retType == null) {
|
||||
LOG.warn("Signature parse error: {}", returnType);
|
||||
SignatureParser sp = SignatureParser.fromNode(this);
|
||||
if (sp == null) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
genericMap = sp.consumeGenericMap();
|
||||
List<ArgType> argsTypes = sp.consumeMethodArgs();
|
||||
retType = sp.consumeType();
|
||||
|
||||
List<ArgType> argsTypes = ArgType.parseSignatureList(argsTypesStr);
|
||||
if (argsTypes == null)
|
||||
return false;
|
||||
|
||||
List<ArgType> mthArgs = mthInfo.getArgumentsTypes();
|
||||
if (argsTypes.size() != mthArgs.size()) {
|
||||
if (argsTypes.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
if (!mthInfo.isConstructor()) {
|
||||
LOG.warn("Wrong signature parse result: " + sign + " -> " + argsTypes
|
||||
+ ", not generic version: " + mthArgs);
|
||||
return false;
|
||||
} else if (getParentClass().getAccessFlags().isEnum()) {
|
||||
// TODO:
|
||||
argsTypes.add(0, mthArgs.get(0));
|
||||
argsTypes.add(1, mthArgs.get(1));
|
||||
} else {
|
||||
// add synthetic arg for outer class
|
||||
argsTypes.add(0, mthArgs.get(0));
|
||||
}
|
||||
List<ArgType> mthArgs = mthInfo.getArgumentsTypes();
|
||||
if (argsTypes.size() != mthArgs.size()) {
|
||||
return false;
|
||||
if (argsTypes.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
if (!mthInfo.isConstructor()) {
|
||||
LOG.warn("Wrong signature parse result: " + sp + " -> " + argsTypes
|
||||
+ ", not generic version: " + mthArgs);
|
||||
return false;
|
||||
} else if (getParentClass().getAccessFlags().isEnum()) {
|
||||
// TODO:
|
||||
argsTypes.add(0, mthArgs.get(0));
|
||||
argsTypes.add(1, mthArgs.get(1));
|
||||
} else {
|
||||
// add synthetic arg for outer class
|
||||
argsTypes.add(0, mthArgs.get(0));
|
||||
}
|
||||
if (argsTypes.size() != mthArgs.size()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
initArguments(argsTypes);
|
||||
} catch (JadxRuntimeException e) {
|
||||
LOG.error("Method signature parse error: " + this, e);
|
||||
return false;
|
||||
}
|
||||
|
||||
initArguments(argsTypes);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -198,8 +183,9 @@ public class MethodNode extends LineAttrNode implements ILoadable {
|
||||
pos = 1;
|
||||
} else {
|
||||
pos = regsCount;
|
||||
for (ArgType arg : args)
|
||||
for (ArgType arg : args) {
|
||||
pos -= arg.getRegCount();
|
||||
}
|
||||
}
|
||||
if (accFlags.isStatic()) {
|
||||
thisArg = null;
|
||||
@@ -290,11 +276,15 @@ public class MethodNode extends LineAttrNode implements ILoadable {
|
||||
// resolve nested try blocks:
|
||||
// inner block contains all handlers from outer block => remove these handlers from inner block
|
||||
// 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()))
|
||||
for (ExceptionHandler h : ct1.getHandlers())
|
||||
for (TryCatchBlock ct1 : catches) {
|
||||
for (TryCatchBlock ct2 : catches) {
|
||||
if (ct1 != ct2 && ct2.getHandlers().containsAll(ct1.getHandlers())) {
|
||||
for (ExceptionHandler h : ct1.getHandlers()) {
|
||||
ct2.removeHandler(this, h);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// attach EXC_HANDLER attributes to instructions
|
||||
@@ -320,8 +310,9 @@ public class MethodNode extends LineAttrNode implements ILoadable {
|
||||
block.addInsn(insnByOffset[offset]);
|
||||
offset = InsnDecoder.getNextInsnOffset(insnByOffset, offset);
|
||||
}
|
||||
if (insnByOffset[end] != null)
|
||||
if (insnByOffset[end] != null) {
|
||||
insnByOffset[end].getAttributes().add(AttributeFlag.TRY_LEAVE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -336,15 +327,17 @@ public class MethodNode extends LineAttrNode implements ILoadable {
|
||||
}
|
||||
// default case
|
||||
int next = InsnDecoder.getNextInsnOffset(insnByOffset, offset);
|
||||
if (next != -1)
|
||||
if (next != -1) {
|
||||
addJump(insnByOffset, offset, next);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case IF:
|
||||
int next = InsnDecoder.getNextInsnOffset(insnByOffset, offset);
|
||||
if (next != -1)
|
||||
if (next != -1) {
|
||||
addJump(insnByOffset, offset, next);
|
||||
}
|
||||
addJump(insnByOffset, offset, ((IfNode) insn).getTarget());
|
||||
break;
|
||||
|
||||
@@ -364,10 +357,11 @@ public class MethodNode extends LineAttrNode implements ILoadable {
|
||||
|
||||
public String getName() {
|
||||
String name = mthInfo.getName();
|
||||
if (name.equals(parentClass.getShortName()))
|
||||
if (name.equals(parentClass.getShortName())) {
|
||||
return name + "_";
|
||||
else
|
||||
} else {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
public ClassNode getParentClass() {
|
||||
@@ -398,8 +392,9 @@ public class MethodNode extends LineAttrNode implements ILoadable {
|
||||
blocks = Collections.unmodifiableList(blocks);
|
||||
exitBlocks = Collections.unmodifiableList(exitBlocks);
|
||||
|
||||
for (BlockNode block : blocks)
|
||||
for (BlockNode block : blocks) {
|
||||
block.lock();
|
||||
}
|
||||
}
|
||||
|
||||
public List<BlockNode> getBasicBlocks() {
|
||||
@@ -431,8 +426,9 @@ public class MethodNode extends LineAttrNode implements ILoadable {
|
||||
|
||||
public LoopAttr getLoopForBlock(BlockNode block) {
|
||||
for (LoopAttr loop : loops) {
|
||||
if (loop.getLoopBlocks().contains(block))
|
||||
if (loop.getLoopBlocks().contains(block)) {
|
||||
return loop;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -442,12 +438,13 @@ public class MethodNode extends LineAttrNode implements ILoadable {
|
||||
}
|
||||
|
||||
public ExceptionHandler addExceptionHandler(ExceptionHandler handler) {
|
||||
if (exceptionHandlers == null) {
|
||||
if (exceptionHandlers.isEmpty()) {
|
||||
exceptionHandlers = new ArrayList<ExceptionHandler>(2);
|
||||
} else {
|
||||
for (ExceptionHandler h : exceptionHandlers) {
|
||||
if (h == handler || h.getHandleOffset() == handler.getHandleOffset())
|
||||
if (h == handler || h.getHandleOffset() == handler.getHandleOffset()) {
|
||||
return h;
|
||||
}
|
||||
}
|
||||
}
|
||||
exceptionHandlers.add(handler);
|
||||
@@ -455,7 +452,7 @@ public class MethodNode extends LineAttrNode implements ILoadable {
|
||||
}
|
||||
|
||||
public List<ExceptionHandler> getExceptionHandlers() {
|
||||
return exceptionHandlers;
|
||||
return Collections.unmodifiableList(exceptionHandlers);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -472,8 +469,9 @@ public class MethodNode extends LineAttrNode implements ILoadable {
|
||||
for (MethodNode method : methods) {
|
||||
if (this != method
|
||||
&& method.getName().equals(name)
|
||||
&& method.mthInfo.getArgumentsTypes().size() == argsCount)
|
||||
&& method.mthInfo.getArgumentsTypes().size() == argsCount) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -486,11 +484,11 @@ public class MethodNode extends LineAttrNode implements ILoadable {
|
||||
return accFlags;
|
||||
}
|
||||
|
||||
public IContainer getRegion() {
|
||||
public Region getRegion() {
|
||||
return region;
|
||||
}
|
||||
|
||||
public void setRegion(IContainer region) {
|
||||
public void setRegion(Region region) {
|
||||
this.region = region;
|
||||
}
|
||||
|
||||
@@ -509,16 +507,20 @@ public class MethodNode extends LineAttrNode implements ILoadable {
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (obj == null || getClass() != obj.getClass()) return false;
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null || getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
MethodNode other = (MethodNode) obj;
|
||||
return mthInfo.equals(other.mthInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return retType
|
||||
+ " " + parentClass.getFullName() + "." + mthInfo.getName()
|
||||
+ "(" + Utils.listToString(mthInfo.getArgumentsTypes()) + ")";
|
||||
return parentClass.getFullName() + "." + mthInfo.getName()
|
||||
+ "(" + Utils.listToString(mthInfo.getArgumentsTypes()) + "):"
|
||||
+ retType;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ public class RootNode {
|
||||
initInnerClasses(classes);
|
||||
}
|
||||
|
||||
private void initClassPath(List<ClassNode> classes) throws IOException, DecodeException {
|
||||
private static void initClassPath(List<ClassNode> classes) throws IOException, DecodeException {
|
||||
ClspGraph clsp = new ClspGraph();
|
||||
clsp.load();
|
||||
clsp.addApp(classes);
|
||||
@@ -59,8 +59,9 @@ public class RootNode {
|
||||
// move inner classes
|
||||
List<ClassNode> inner = new ArrayList<ClassNode>();
|
||||
for (ClassNode cls : classes) {
|
||||
if (cls.getClassInfo().isInner())
|
||||
if (cls.getClassInfo().isInner()) {
|
||||
inner.add(cls);
|
||||
}
|
||||
}
|
||||
for (ClassNode cls : inner) {
|
||||
ClassNode parent = resolveClass(cls.getClassInfo().getParentClass());
|
||||
@@ -81,8 +82,9 @@ public class RootNode {
|
||||
if (includeInner) {
|
||||
classes.add(cls);
|
||||
} else {
|
||||
if (!cls.getClassInfo().isInner())
|
||||
if (!cls.getClassInfo().isInner()) {
|
||||
classes.add(cls);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import jadx.core.dex.attributes.annotations.Annotation;
|
||||
import jadx.core.dex.attributes.annotations.Annotation.Visibility;
|
||||
import jadx.core.dex.attributes.annotations.AnnotationsList;
|
||||
import jadx.core.dex.attributes.annotations.MethodParameters;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.DexNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
@@ -19,10 +20,21 @@ import com.android.dx.io.DexBuffer.Section;
|
||||
|
||||
public class AnnotationsParser {
|
||||
|
||||
private final DexNode dex;
|
||||
private static final Annotation.Visibility[] VISIBILITIES = {
|
||||
Visibility.BUILD,
|
||||
Visibility.RUNTIME,
|
||||
Visibility.SYSTEM
|
||||
};
|
||||
|
||||
public AnnotationsParser(ClassNode cls, int offset) throws DecodeException {
|
||||
private final DexNode dex;
|
||||
private final ClassNode cls;
|
||||
|
||||
public AnnotationsParser(ClassNode cls) {
|
||||
this.cls = cls;
|
||||
this.dex = cls.dex();
|
||||
}
|
||||
|
||||
public void parse(int offset) throws DecodeException {
|
||||
Section section = dex.openSection(offset);
|
||||
|
||||
// TODO read as unsigned int
|
||||
@@ -61,8 +73,6 @@ public class AnnotationsParser {
|
||||
private AnnotationsList readAnnotationSet(int offset) throws DecodeException {
|
||||
Section section = dex.openSection(offset);
|
||||
int size = section.readInt();
|
||||
if (size > 100)
|
||||
section.toString();
|
||||
List<Annotation> list = new ArrayList<Annotation>(size);
|
||||
for (int i = 0; i < size; i++) {
|
||||
Section anSection = dex.openSection(section.readInt());
|
||||
@@ -72,12 +82,6 @@ public class AnnotationsParser {
|
||||
return new AnnotationsList(list);
|
||||
}
|
||||
|
||||
private static final Annotation.Visibility[] VISIBILITIES = new Annotation.Visibility[]{
|
||||
Annotation.Visibility.BUILD,
|
||||
Annotation.Visibility.RUNTIME,
|
||||
Annotation.Visibility.SYSTEM
|
||||
};
|
||||
|
||||
public static Annotation readAnnotation(DexNode dex, Section s, boolean readVisibility) throws DecodeException {
|
||||
EncValueParser parser = new EncValueParser(dex, s);
|
||||
Visibility visibility = null;
|
||||
@@ -91,6 +95,11 @@ public class AnnotationsParser {
|
||||
String name = dex.getString(s.readUleb128());
|
||||
values.put(name, parser.parseValue());
|
||||
}
|
||||
return new Annotation(visibility, dex.getType(typeIndex), values);
|
||||
ArgType type = dex.getType(typeIndex);
|
||||
Annotation annotation = new Annotation(visibility, type, values);
|
||||
if (!type.isObject()) {
|
||||
throw new DecodeException("Incorrect type for annotation: " + annotation);
|
||||
}
|
||||
return annotation;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,9 +25,12 @@ public class DebugInfoParser {
|
||||
private static final int DBG_SET_EPILOGUE_BEGIN = 0x08;
|
||||
private static final int DBG_SET_FILE = 0x09;
|
||||
|
||||
private static final int DBG_FIRST_SPECIAL = 0x0a; // the smallest special opcode
|
||||
private static final int DBG_LINE_BASE = -4; // the smallest line number increment
|
||||
private static final int DBG_LINE_RANGE = 15; // the number of line increments represented
|
||||
// the smallest special opcode
|
||||
private static final int DBG_FIRST_SPECIAL = 0x0a;
|
||||
// the smallest line number increment
|
||||
private static final int DBG_LINE_BASE = -4;
|
||||
// the number of line increments represented
|
||||
private static final int DBG_LINE_RANGE = 15;
|
||||
|
||||
private final MethodNode mth;
|
||||
private final Section section;
|
||||
@@ -42,8 +45,9 @@ public class DebugInfoParser {
|
||||
this.dex = mth.dex();
|
||||
this.section = dex.openSection(debugOffset);
|
||||
|
||||
this.locals = new LocalVar[mth.getRegsCount()];
|
||||
this.activeRegisters = new InsnArg[mth.getRegsCount()];
|
||||
int regsCount = mth.getRegsCount();
|
||||
this.locals = new LocalVar[regsCount];
|
||||
this.activeRegisters = new InsnArg[regsCount];
|
||||
this.insnByOffset = insnByOffset;
|
||||
}
|
||||
|
||||
@@ -51,7 +55,7 @@ public class DebugInfoParser {
|
||||
int addr = 0;
|
||||
int line = section.readUleb128();
|
||||
|
||||
int paramsCount = section.readUleb128(); // exclude 'this'
|
||||
int paramsCount = section.readUleb128();
|
||||
List<RegisterArg> mthArgs = mth.getArguments(false);
|
||||
assert paramsCount == mthArgs.size();
|
||||
|
||||
@@ -69,7 +73,8 @@ public class DebugInfoParser {
|
||||
activeRegisters[rn] = arg;
|
||||
}
|
||||
|
||||
addrChange(-1, 1, line); // process '0' instruction
|
||||
// process '0' instruction
|
||||
addrChange(-1, 1, line);
|
||||
|
||||
int c = section.readByte() & 0xFF;
|
||||
while (c != DBG_END_SEQUENCE) {
|
||||
@@ -139,7 +144,7 @@ public class DebugInfoParser {
|
||||
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);
|
||||
int addrInc = adjustedOpcode / DBG_LINE_RANGE;
|
||||
addr = addrChange(addr, addrInc, line);
|
||||
} else {
|
||||
throw new DecodeException("Unknown debug insn code: " + c);
|
||||
@@ -162,18 +167,19 @@ public class DebugInfoParser {
|
||||
int newAddr = addr + addrInc;
|
||||
for (int i = addr + 1; i <= newAddr; i++) {
|
||||
InsnNode insn = insnByOffset[i];
|
||||
if (insn == null)
|
||||
if (insn == null) {
|
||||
continue;
|
||||
|
||||
}
|
||||
insn.setSourceLine(line);
|
||||
for (InsnArg arg : insn.getArguments())
|
||||
for (InsnArg arg : insn.getArguments()) {
|
||||
if (arg.isRegister()) {
|
||||
activeRegisters[((RegisterArg) arg).getRegNum()] = arg;
|
||||
}
|
||||
|
||||
}
|
||||
RegisterArg res = insn.getResult();
|
||||
if (res != null)
|
||||
if (res != null) {
|
||||
activeRegisters[res.getRegNum()] = res;
|
||||
}
|
||||
}
|
||||
return newAddr;
|
||||
}
|
||||
@@ -195,25 +201,27 @@ public class DebugInfoParser {
|
||||
|
||||
for (int i = start; i <= end; i++) {
|
||||
InsnNode insn = insnByOffset[i];
|
||||
if (insn != null)
|
||||
if (insn != null) {
|
||||
fillLocals(insn, var);
|
||||
}
|
||||
}
|
||||
merge(activeRegisters[var.getRegNum()], var);
|
||||
}
|
||||
|
||||
private static void fillLocals(InsnNode insn, LocalVar var) {
|
||||
if (insn.getResult() != null)
|
||||
if (insn.getResult() != null) {
|
||||
merge(insn.getResult(), var);
|
||||
|
||||
for (InsnArg arg : insn.getArguments())
|
||||
}
|
||||
for (InsnArg arg : insn.getArguments()) {
|
||||
merge(arg, var);
|
||||
}
|
||||
}
|
||||
|
||||
private static void merge(InsnArg arg, LocalVar var) {
|
||||
if (arg != null && arg.isRegister()) {
|
||||
if (var.getRegNum() == ((RegisterArg) arg).getRegNum()) {
|
||||
arg.mergeDebugInfo(var);
|
||||
}
|
||||
if (arg != null
|
||||
&& arg.isRegister()
|
||||
&& var.getRegNum() == ((RegisterArg) arg).getRegNum()) {
|
||||
arg.mergeDebugInfo(var);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,8 +6,13 @@ 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 {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(LocalVar.class);
|
||||
|
||||
private boolean isEnd;
|
||||
|
||||
private int startAddr;
|
||||
@@ -29,13 +34,37 @@ final class LocalVar extends RegisterArg {
|
||||
|
||||
private void init(String name, ArgType type, String sign) {
|
||||
if (sign != null) {
|
||||
type = ArgType.generic(sign);
|
||||
try {
|
||||
ArgType gType = ArgType.generic(sign);
|
||||
if (checkSignature(type, sign, gType)) {
|
||||
type = gType;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.error("Can't parse signature for local variable: " + sign, e);
|
||||
}
|
||||
}
|
||||
TypedVar tv = new TypedVar(type);
|
||||
tv.setName(name);
|
||||
forceSetTypedVar(tv);
|
||||
}
|
||||
|
||||
private boolean checkSignature(ArgType type, String sign, ArgType gType) {
|
||||
boolean apply;
|
||||
ArgType el = gType.getArrayRootElement();
|
||||
if (el.isGeneric()) {
|
||||
if (!type.getArrayRootElement().getObject().equals(el.getObject())) {
|
||||
LOG.warn("Generic type in debug info not equals: {} != {}", type, gType);
|
||||
}
|
||||
apply = true;
|
||||
} else if (el.isGenericType()) {
|
||||
apply = true;
|
||||
} else {
|
||||
LOG.debug("Local var signature from debug info not generic: {}, parsed: {}", sign, gType);
|
||||
apply = false;
|
||||
}
|
||||
return apply;
|
||||
}
|
||||
|
||||
public void start(int addr, int line) {
|
||||
this.isEnd = false;
|
||||
this.startAddr = addr;
|
||||
|
||||
@@ -0,0 +1,288 @@
|
||||
package jadx.core.dex.nodes.parser;
|
||||
|
||||
import jadx.core.Consts;
|
||||
import jadx.core.dex.attributes.IAttributeNode;
|
||||
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;
|
||||
|
||||
public class SignatureParser {
|
||||
private static final char STOP_CHAR = 0;
|
||||
|
||||
private final String sign;
|
||||
private final int end;
|
||||
private int pos;
|
||||
private int mark;
|
||||
|
||||
public SignatureParser(String signature) {
|
||||
sign = signature;
|
||||
end = sign.length();
|
||||
pos = -1;
|
||||
mark = 0;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static SignatureParser fromNode(IAttributeNode node) {
|
||||
Annotation a = node.getAttributes().getAnnotation(Consts.DALVIK_SIGNATURE);
|
||||
if (a == null) {
|
||||
return null;
|
||||
}
|
||||
return new SignatureParser(mergeSignature((List<String>) a.getDefaultValue()));
|
||||
}
|
||||
|
||||
private char next() {
|
||||
pos++;
|
||||
if (pos >= end) {
|
||||
return STOP_CHAR;
|
||||
}
|
||||
return sign.charAt(pos);
|
||||
}
|
||||
|
||||
private boolean lookAhead(char ch) {
|
||||
int next = pos + 1;
|
||||
return next < end && sign.charAt(next) == ch;
|
||||
}
|
||||
|
||||
private void mark() {
|
||||
mark = pos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Exclusive slice.
|
||||
*
|
||||
* @return string from 'mark' to current position (not including current character)
|
||||
*/
|
||||
private String slice() {
|
||||
if (mark >= pos) {
|
||||
return "";
|
||||
}
|
||||
return sign.substring(mark, pos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inclusive slice (includes current character)
|
||||
*/
|
||||
private String inclusiveSlice() {
|
||||
if (mark >= pos) {
|
||||
return "";
|
||||
}
|
||||
return sign.substring(mark, pos + 1);
|
||||
}
|
||||
|
||||
private boolean forwardTo(char lastChar) {
|
||||
int startPos = pos;
|
||||
char ch;
|
||||
while ((ch = next()) != STOP_CHAR) {
|
||||
if (ch == lastChar) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
pos = startPos;
|
||||
return false;
|
||||
}
|
||||
|
||||
private void consume(char exp) {
|
||||
char c = next();
|
||||
if (exp != c) {
|
||||
throw new JadxRuntimeException("Consume wrong char: '" + c + "' != '" + exp
|
||||
+ "', sign: " + debugString());
|
||||
}
|
||||
}
|
||||
|
||||
private boolean tryConsume(char exp) {
|
||||
if (lookAhead(exp)) {
|
||||
next();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private String consumeUntil(char lastChar) {
|
||||
mark();
|
||||
return forwardTo(lastChar) ? slice() : null;
|
||||
}
|
||||
|
||||
public ArgType consumeType() {
|
||||
char ch = next();
|
||||
mark();
|
||||
switch (ch) {
|
||||
case 'L':
|
||||
ArgType obj = consumeObjectType(false);
|
||||
if (obj != null) {
|
||||
return obj;
|
||||
}
|
||||
break;
|
||||
case 'T':
|
||||
next();
|
||||
mark();
|
||||
if (forwardTo(';')) {
|
||||
return ArgType.genericType(slice());
|
||||
}
|
||||
break;
|
||||
case '[':
|
||||
return ArgType.array(consumeType());
|
||||
|
||||
case STOP_CHAR:
|
||||
return null;
|
||||
|
||||
default:
|
||||
// primitive type (one char)
|
||||
ArgType type = ArgType.parse(ch);
|
||||
if (type != null) {
|
||||
return type;
|
||||
}
|
||||
break;
|
||||
}
|
||||
throw new JadxRuntimeException("Can't parse type: " + debugString());
|
||||
}
|
||||
|
||||
private ArgType consumeObjectType(boolean incompleteType) {
|
||||
mark();
|
||||
int ch;
|
||||
do {
|
||||
ch = next();
|
||||
if (ch == STOP_CHAR) {
|
||||
return null;
|
||||
}
|
||||
} while (ch != '<' && ch != ';');
|
||||
|
||||
if (ch == ';') {
|
||||
return ArgType.object(incompleteType ? slice() : inclusiveSlice());
|
||||
} else {
|
||||
// generic type start ('<')
|
||||
String obj = slice();
|
||||
if (!incompleteType) {
|
||||
obj += ";";
|
||||
}
|
||||
ArgType[] genArr = consumeGenericArgs();
|
||||
consume('>');
|
||||
|
||||
ArgType genericType = ArgType.generic(obj, genArr);
|
||||
if (lookAhead('.')) {
|
||||
consume('.');
|
||||
next();
|
||||
// type parsing not completed, proceed to inner class
|
||||
ArgType inner = consumeObjectType(true);
|
||||
return ArgType.genericInner(genericType, inner.getObject(), inner.getGenericTypes());
|
||||
} else {
|
||||
consume(';');
|
||||
return genericType;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ArgType[] consumeGenericArgs() {
|
||||
List<ArgType> list = new ArrayList<ArgType>(1);
|
||||
ArgType type;
|
||||
do {
|
||||
if (lookAhead('*')) {
|
||||
next();
|
||||
type = ArgType.wildcard();
|
||||
} else if (lookAhead('+')) {
|
||||
next();
|
||||
type = ArgType.wildcard(consumeType(), 1);
|
||||
} else if (lookAhead('-')) {
|
||||
next();
|
||||
type = ArgType.wildcard(consumeType(), -1);
|
||||
} else {
|
||||
type = consumeType();
|
||||
}
|
||||
if (type != null) {
|
||||
list.add(type);
|
||||
}
|
||||
} while (type != null && !lookAhead('>'));
|
||||
return list.toArray(new ArgType[list.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Map of generic types names to extends classes.
|
||||
* <p/>
|
||||
* Example: "<T:Ljava/lang/Exception;:Ljava/lang/Object;>"
|
||||
*/
|
||||
public Map<ArgType, List<ArgType>> consumeGenericMap() {
|
||||
if (!lookAhead('<')) {
|
||||
return null;
|
||||
}
|
||||
Map<ArgType, List<ArgType>> map = new LinkedHashMap<ArgType, List<ArgType>>(2);
|
||||
consume('<');
|
||||
while (true) {
|
||||
if (lookAhead('>') || next() == STOP_CHAR) {
|
||||
break;
|
||||
}
|
||||
String id = consumeUntil(':');
|
||||
tryConsume(':');
|
||||
List<ArgType> types = consumeExtendsTypesList();
|
||||
map.put(ArgType.genericType(id), types);
|
||||
}
|
||||
consume('>');
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* List of types separated by ':' last type is 'java.lang.Object'.
|
||||
* <p/>
|
||||
* Example: "Ljava/lang/Exception;:Ljava/lang/Object;"
|
||||
*/
|
||||
private List<ArgType> consumeExtendsTypesList() {
|
||||
List<ArgType> types = Collections.emptyList();
|
||||
boolean next;
|
||||
do {
|
||||
ArgType argType = consumeType();
|
||||
if (!argType.equals(ArgType.OBJECT)) {
|
||||
if (types.isEmpty()) {
|
||||
types = new LinkedList<ArgType>();
|
||||
}
|
||||
types.add(argType);
|
||||
}
|
||||
next = lookAhead(':');
|
||||
if (next) {
|
||||
consume(':');
|
||||
}
|
||||
} while (next);
|
||||
return types;
|
||||
}
|
||||
|
||||
public List<ArgType> consumeMethodArgs() {
|
||||
consume('(');
|
||||
if (lookAhead(')')) {
|
||||
consume(')');
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<ArgType> args = new LinkedList<ArgType>();
|
||||
do {
|
||||
args.add(consumeType());
|
||||
} while (!lookAhead(')'));
|
||||
consume(')');
|
||||
return args;
|
||||
}
|
||||
|
||||
private static String mergeSignature(List<String> list) {
|
||||
if (list.size() == 1) {
|
||||
return list.get(0);
|
||||
}
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (String s : list) {
|
||||
sb.append(s);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private String debugString() {
|
||||
return sign + " at position " + pos + " ('" + sign.charAt(pos) + "')";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (pos == -1) {
|
||||
return sign;
|
||||
}
|
||||
return sign.substring(0, mark) + '{' + sign.substring(mark, pos) + '}' + sign.substring(pos);
|
||||
}
|
||||
}
|
||||
@@ -15,13 +15,12 @@ public class StaticValuesParser extends EncValueParser {
|
||||
super(dex, in);
|
||||
}
|
||||
|
||||
public void processFields(List<FieldNode> fields) throws DecodeException {
|
||||
int size = Leb128Utils.readUnsignedLeb128(in);
|
||||
visitArray(size);
|
||||
|
||||
for (int i = 0; i < size; i++) {
|
||||
public int processFields(List<FieldNode> fields) throws DecodeException {
|
||||
int count = Leb128Utils.readUnsignedLeb128(in);
|
||||
for (int i = 0; i < count; i++) {
|
||||
Object value = parseValue();
|
||||
fields.get(i).getAttributes().add(new FieldValueAttr(value));
|
||||
}
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
package jadx.core.dex.regions;
|
||||
|
||||
import jadx.core.dex.attributes.AttrNode;
|
||||
import jadx.core.dex.nodes.IContainer;
|
||||
import jadx.core.dex.nodes.IRegion;
|
||||
|
||||
public abstract class AbstractRegion extends AttrNode implements IRegion {
|
||||
|
||||
private final IRegion parent;
|
||||
private IRegion parent;
|
||||
|
||||
public AbstractRegion(IRegion parent) {
|
||||
this.parent = parent;
|
||||
@@ -16,4 +17,13 @@ public abstract class AbstractRegion extends AttrNode implements IRegion {
|
||||
return parent;
|
||||
}
|
||||
|
||||
public void setParent(IRegion parent) {
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean replaceSubBlock(IContainer oldBlock, IContainer newBlock) {
|
||||
// TODO: implement for others regions
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package jadx.core.dex.regions;
|
||||
|
||||
import jadx.core.dex.instructions.IfNode;
|
||||
import jadx.core.dex.instructions.IfOp;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.LiteralArg;
|
||||
|
||||
public final class Compare {
|
||||
private final IfNode insn;
|
||||
|
||||
public Compare(IfNode insn) {
|
||||
this.insn = insn;
|
||||
}
|
||||
|
||||
public IfOp getOp() {
|
||||
return insn.getOp();
|
||||
}
|
||||
|
||||
public InsnArg getA() {
|
||||
return insn.getArg(0);
|
||||
}
|
||||
|
||||
public InsnArg getB() {
|
||||
return insn.getArg(1);
|
||||
}
|
||||
|
||||
public Compare invert() {
|
||||
insn.invertCondition();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change 'a != false' to 'a == true'
|
||||
*/
|
||||
public void normalize() {
|
||||
if (getOp() == IfOp.NE && getB().isLiteral() && getB().equals(LiteralArg.FALSE)) {
|
||||
insn.changeCondition(IfOp.EQ, getA(), LiteralArg.TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getA() + " " + getOp().getSymbol() + " " + getB();
|
||||
}
|
||||
}
|
||||
@@ -3,15 +3,53 @@ package jadx.core.dex.regions;
|
||||
import jadx.core.dex.instructions.IfNode;
|
||||
import jadx.core.dex.instructions.IfOp;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.LiteralArg;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public final class IfCondition {
|
||||
|
||||
public static enum Mode {
|
||||
COMPARE,
|
||||
NOT,
|
||||
AND,
|
||||
OR
|
||||
}
|
||||
|
||||
private final Mode mode;
|
||||
private final List<IfCondition> args;
|
||||
private final Compare compare;
|
||||
|
||||
private IfCondition(Compare compare) {
|
||||
this.mode = Mode.COMPARE;
|
||||
this.compare = compare;
|
||||
this.args = Collections.emptyList();
|
||||
}
|
||||
|
||||
private IfCondition(Mode mode, List<IfCondition> args) {
|
||||
this.mode = mode;
|
||||
this.args = args;
|
||||
this.compare = null;
|
||||
}
|
||||
|
||||
private IfCondition(IfCondition c) {
|
||||
this.mode = c.mode;
|
||||
this.compare = c.compare;
|
||||
if (c.mode == Mode.COMPARE) {
|
||||
this.args = Collections.emptyList();
|
||||
} else {
|
||||
this.args = new ArrayList<IfCondition>(c.args);
|
||||
}
|
||||
}
|
||||
|
||||
public static IfCondition fromIfBlock(BlockNode header) {
|
||||
if (header == null) {
|
||||
return null;
|
||||
@@ -37,67 +75,6 @@ public final class IfCondition {
|
||||
}
|
||||
}
|
||||
|
||||
public static final class Compare {
|
||||
private final IfNode insn;
|
||||
|
||||
public Compare(IfNode insn) {
|
||||
this.insn = insn;
|
||||
}
|
||||
|
||||
public IfOp getOp() {
|
||||
return insn.getOp();
|
||||
}
|
||||
|
||||
public InsnArg getA() {
|
||||
return insn.getArg(0);
|
||||
}
|
||||
|
||||
public InsnArg getB() {
|
||||
if (insn.isZeroCmp())
|
||||
return InsnArg.lit(0, getA().getType());
|
||||
else
|
||||
return insn.getArg(1);
|
||||
}
|
||||
public Compare invert() {
|
||||
insn.invertCondition();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getA() + " " + getOp().getSymbol() + " " + getB();
|
||||
}
|
||||
}
|
||||
|
||||
public static enum Mode {
|
||||
COMPARE,
|
||||
NOT,
|
||||
AND,
|
||||
OR
|
||||
}
|
||||
|
||||
private final Mode mode;
|
||||
private final List<IfCondition> args;
|
||||
private final Compare compare;
|
||||
|
||||
private IfCondition(Compare compare) {
|
||||
this.mode = Mode.COMPARE;
|
||||
this.compare = compare;
|
||||
this.args = null;
|
||||
}
|
||||
|
||||
private IfCondition(Mode mode, List<IfCondition> args) {
|
||||
this.mode = mode;
|
||||
this.args = args;
|
||||
this.compare = null;
|
||||
}
|
||||
|
||||
private IfCondition(IfCondition c) {
|
||||
this.mode = c.mode;
|
||||
this.compare = c.compare;
|
||||
this.args = new ArrayList<IfCondition>(c.args);
|
||||
}
|
||||
|
||||
public Mode getMode() {
|
||||
return mode;
|
||||
}
|
||||
@@ -106,6 +83,14 @@ public final class IfCondition {
|
||||
return args;
|
||||
}
|
||||
|
||||
public IfCondition first() {
|
||||
return args.get(0);
|
||||
}
|
||||
|
||||
public IfCondition second() {
|
||||
return args.get(1);
|
||||
}
|
||||
|
||||
public void addArg(IfCondition c) {
|
||||
args.add(c);
|
||||
}
|
||||
@@ -118,34 +103,120 @@ public final class IfCondition {
|
||||
return compare;
|
||||
}
|
||||
|
||||
public IfCondition invert() {
|
||||
public static IfCondition invert(IfCondition cond) {
|
||||
Mode mode = cond.getMode();
|
||||
switch (mode) {
|
||||
case COMPARE:
|
||||
return new IfCondition(compare.invert());
|
||||
return new IfCondition(cond.getCompare().invert());
|
||||
case NOT:
|
||||
return new IfCondition(args.get(0));
|
||||
return cond.first();
|
||||
case AND:
|
||||
case OR:
|
||||
List<IfCondition> args = cond.getArgs();
|
||||
List<IfCondition> newArgs = new ArrayList<IfCondition>(args.size());
|
||||
for (IfCondition arg : args) {
|
||||
newArgs.add(arg.invert());
|
||||
newArgs.add(invert(arg));
|
||||
}
|
||||
return new IfCondition(mode == Mode.AND ? Mode.OR : Mode.AND, newArgs);
|
||||
}
|
||||
throw new JadxRuntimeException("Unknown mode for invert: " + mode);
|
||||
}
|
||||
|
||||
public static IfCondition not(IfCondition cond) {
|
||||
if (cond.getMode() == Mode.NOT) {
|
||||
return cond.first();
|
||||
}
|
||||
return new IfCondition(Mode.NOT, Collections.singletonList(cond));
|
||||
}
|
||||
|
||||
public static IfCondition simplify(IfCondition cond) {
|
||||
if (cond.isCompare()) {
|
||||
Compare c = cond.getCompare();
|
||||
if (c.getOp() == IfOp.EQ && c.getB().isLiteral() && c.getB().equals(LiteralArg.FALSE)) {
|
||||
return not(new IfCondition(c.invert()));
|
||||
} else {
|
||||
c.normalize();
|
||||
}
|
||||
return cond;
|
||||
}
|
||||
List<IfCondition> args = null;
|
||||
for (int i = 0; i < cond.getArgs().size(); i++) {
|
||||
IfCondition arg = cond.getArgs().get(i);
|
||||
IfCondition simpl = simplify(arg);
|
||||
if (simpl != arg) {
|
||||
if (args == null) {
|
||||
args = new ArrayList<IfCondition>(cond.getArgs());
|
||||
}
|
||||
args.set(i, simpl);
|
||||
}
|
||||
}
|
||||
if (args != null) {
|
||||
// arguments was changed
|
||||
cond = new IfCondition(cond.getMode(), args);
|
||||
}
|
||||
if (cond.getMode() == Mode.NOT && cond.first().getMode() == Mode.NOT) {
|
||||
cond = cond.first().first();
|
||||
}
|
||||
|
||||
// for condition with a lot of negations => make invert
|
||||
if (cond.getMode() == Mode.OR || cond.getMode() == Mode.AND) {
|
||||
int count = cond.getArgs().size();
|
||||
if (count > 1) {
|
||||
int negCount = 0;
|
||||
for (IfCondition arg : cond.getArgs()) {
|
||||
if (arg.getMode() == Mode.NOT
|
||||
|| (arg.isCompare() && arg.getCompare().getOp() == IfOp.NE)) {
|
||||
negCount++;
|
||||
}
|
||||
}
|
||||
if (negCount > count / 2) {
|
||||
return not(invert(cond));
|
||||
}
|
||||
}
|
||||
}
|
||||
return cond;
|
||||
}
|
||||
|
||||
public List<RegisterArg> getRegisterArgs() {
|
||||
List<RegisterArg> list = new LinkedList<RegisterArg>();
|
||||
if (mode == Mode.COMPARE) {
|
||||
InsnArg a = compare.getA();
|
||||
if (a.isRegister()) {
|
||||
list.add((RegisterArg) a);
|
||||
}
|
||||
InsnArg b = compare.getA();
|
||||
if (a.isRegister()) {
|
||||
list.add((RegisterArg) b);
|
||||
}
|
||||
} else {
|
||||
for (IfCondition arg : args) {
|
||||
list.addAll(arg.getRegisterArgs());
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
switch (mode) {
|
||||
case COMPARE:
|
||||
return compare.toString();
|
||||
case NOT:
|
||||
return "!" + args;
|
||||
return "!" + first();
|
||||
case AND:
|
||||
return "&& " + args;
|
||||
case OR:
|
||||
return "|| " + args;
|
||||
String op = mode == Mode.OR ? " || " : " && ";
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append('(');
|
||||
for (Iterator<IfCondition> it = args.iterator(); it.hasNext(); ) {
|
||||
IfCondition arg = it.next();
|
||||
sb.append(arg);
|
||||
if (it.hasNext()) {
|
||||
sb.append(op);
|
||||
}
|
||||
}
|
||||
sb.append(')');
|
||||
return sb.toString();
|
||||
}
|
||||
return "??";
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@ public final class IfRegion extends AbstractRegion {
|
||||
private IContainer thenRegion;
|
||||
private IContainer elseRegion;
|
||||
|
||||
private TernaryRegion ternRegion;
|
||||
|
||||
public IfRegion(IRegion parent, BlockNode header) {
|
||||
super(parent);
|
||||
assert header.getInstructions().size() == 1;
|
||||
@@ -47,19 +49,56 @@ public final class IfRegion extends AbstractRegion {
|
||||
this.elseRegion = elseRegion;
|
||||
}
|
||||
|
||||
public BlockNode getHeader() {
|
||||
return header;
|
||||
}
|
||||
|
||||
public void setTernRegion(TernaryRegion ternRegion) {
|
||||
this.ternRegion = ternRegion;
|
||||
}
|
||||
|
||||
public TernaryRegion getTernRegion() {
|
||||
return ternRegion;
|
||||
}
|
||||
|
||||
public boolean simplifyCondition() {
|
||||
IfCondition cond = IfCondition.simplify(condition);
|
||||
if (cond != condition) {
|
||||
condition = cond;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void invert() {
|
||||
condition = IfCondition.invert(condition);
|
||||
// swap regions
|
||||
IContainer tmp = thenRegion;
|
||||
thenRegion = elseRegion;
|
||||
elseRegion = tmp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<IContainer> getSubBlocks() {
|
||||
if (ternRegion != null) {
|
||||
return ternRegion.getSubBlocks();
|
||||
}
|
||||
ArrayList<IContainer> all = new ArrayList<IContainer>(3);
|
||||
all.add(header);
|
||||
if (thenRegion != null)
|
||||
if (thenRegion != null) {
|
||||
all.add(thenRegion);
|
||||
if (elseRegion != null)
|
||||
}
|
||||
if (elseRegion != null) {
|
||||
all.add(elseRegion);
|
||||
}
|
||||
return Collections.unmodifiableList(all);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (ternRegion != null) {
|
||||
return ternRegion.toString();
|
||||
}
|
||||
return "IF(" + condition + ") then " + thenRegion + " else " + elseRegion;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,9 +15,9 @@ public final class LoopRegion extends AbstractRegion {
|
||||
|
||||
// loop header contains one 'if' insn, equals null for infinite loop
|
||||
private IfCondition condition;
|
||||
private BlockNode conditionBlock;
|
||||
private final BlockNode conditionBlock;
|
||||
// instruction which must be executed before condition in every loop
|
||||
private BlockNode preCondition = null;
|
||||
private BlockNode preCondition;
|
||||
private IContainer body;
|
||||
private final boolean conditionAtEnd;
|
||||
|
||||
@@ -68,9 +68,9 @@ public final class LoopRegion extends AbstractRegion {
|
||||
*/
|
||||
public boolean checkPreCondition() {
|
||||
List<InsnNode> insns = preCondition.getInstructions();
|
||||
if (insns.isEmpty())
|
||||
if (insns.isEmpty()) {
|
||||
return true;
|
||||
|
||||
}
|
||||
IfNode ifInsn = getIfInsn();
|
||||
int size = insns.size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
@@ -79,21 +79,23 @@ public final class LoopRegion extends AbstractRegion {
|
||||
return false;
|
||||
} else {
|
||||
RegisterArg res = insn.getResult();
|
||||
if (res.getTypedVar().getUseList().size() > 2)
|
||||
if (res.getTypedVar().getUseList().size() > 2) {
|
||||
return false;
|
||||
|
||||
}
|
||||
boolean found = false;
|
||||
// search result arg in other insns
|
||||
for (int j = i + 1; j < size; j++) {
|
||||
if (insns.get(i).containsArg(res))
|
||||
if (insns.get(i).containsArg(res)) {
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
// or in if insn
|
||||
if (!found && ifInsn.containsArg(res))
|
||||
if (!found && ifInsn.containsArg(res)) {
|
||||
found = true;
|
||||
|
||||
if (!found)
|
||||
}
|
||||
if (!found) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
@@ -117,10 +119,12 @@ public final class LoopRegion extends AbstractRegion {
|
||||
@Override
|
||||
public List<IContainer> getSubBlocks() {
|
||||
List<IContainer> all = new ArrayList<IContainer>(3);
|
||||
if (preCondition != null)
|
||||
if (preCondition != null) {
|
||||
all.add(preCondition);
|
||||
if (conditionBlock != null)
|
||||
}
|
||||
if (conditionBlock != null) {
|
||||
all.add(conditionBlock);
|
||||
}
|
||||
all.add(body);
|
||||
return Collections.unmodifiableList(all);
|
||||
}
|
||||
|
||||
@@ -21,6 +21,23 @@ public final class Region extends AbstractRegion {
|
||||
return blocks;
|
||||
}
|
||||
|
||||
public void add(IContainer region) {
|
||||
if (region instanceof AbstractRegion) {
|
||||
((AbstractRegion) region).setParent(this);
|
||||
}
|
||||
blocks.add(region);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean replaceSubBlock(IContainer oldBlock, IContainer newBlock) {
|
||||
int i = blocks.indexOf(oldBlock);
|
||||
if (i != -1) {
|
||||
blocks.set(i, newBlock);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
@@ -28,11 +45,11 @@ public final class Region extends AbstractRegion {
|
||||
sb.append(blocks.size());
|
||||
if (blocks.size() != 0) {
|
||||
for (IContainer cont : blocks) {
|
||||
if (cont instanceof BlockNode)
|
||||
if (cont instanceof BlockNode) {
|
||||
sb.append(((BlockNode) cont).getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -53,8 +53,9 @@ public final class SwitchRegion extends AbstractRegion {
|
||||
List<IContainer> all = new ArrayList<IContainer>(cases.size() + 2);
|
||||
all.add(header);
|
||||
all.addAll(cases);
|
||||
if (defCase != null)
|
||||
if (defCase != null) {
|
||||
all.add(defCase);
|
||||
}
|
||||
return Collections.unmodifiableList(all);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,21 +4,27 @@ import jadx.core.dex.nodes.IContainer;
|
||||
import jadx.core.dex.nodes.IRegion;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public final class SynchronizedRegion extends AbstractRegion {
|
||||
|
||||
private final InsnNode insn;
|
||||
private final InsnNode enterInsn;
|
||||
private final List<InsnNode> exitInsns = new LinkedList<InsnNode>();
|
||||
private final Region region;
|
||||
|
||||
public SynchronizedRegion(IRegion parent, InsnNode insn) {
|
||||
super(parent);
|
||||
this.insn = insn;
|
||||
this.enterInsn = insn;
|
||||
this.region = new Region(this);
|
||||
}
|
||||
|
||||
public InsnNode getInsn() {
|
||||
return insn;
|
||||
public InsnNode getEnterInsn() {
|
||||
return enterInsn;
|
||||
}
|
||||
|
||||
public List<InsnNode> getExitInsns() {
|
||||
return exitInsns;
|
||||
}
|
||||
|
||||
public Region getRegion() {
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package jadx.core.dex.regions;
|
||||
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.dex.nodes.IBlock;
|
||||
import jadx.core.dex.nodes.IContainer;
|
||||
import jadx.core.dex.nodes.IRegion;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public final class TernaryRegion extends AbstractRegion {
|
||||
private final IBlock container;
|
||||
|
||||
public TernaryRegion(IRegion parent, BlockNode block) {
|
||||
super(parent);
|
||||
this.container = block;
|
||||
}
|
||||
|
||||
public IBlock getBlock() {
|
||||
return container;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<IContainer> getSubBlocks() {
|
||||
return Collections.singletonList((IContainer) container);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TERN:" + container;
|
||||
}
|
||||
}
|
||||
@@ -86,13 +86,23 @@ public class ExceptionHandler {
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (obj == null) return false;
|
||||
if (getClass() != obj.getClass()) return false;
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
ExceptionHandler other = (ExceptionHandler) obj;
|
||||
if (catchType == null) {
|
||||
if (other.catchType != null) return false;
|
||||
} else if (!catchType.equals(other.catchType)) return false;
|
||||
if (other.catchType != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!catchType.equals(other.catchType)) {
|
||||
return false;
|
||||
}
|
||||
return handleOffset == other.handleOffset;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import jadx.core.dex.nodes.IContainer;
|
||||
import jadx.core.dex.nodes.InsnContainer;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.visitors.InstructionRemover;
|
||||
import jadx.core.utils.InstructionRemover;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
@@ -12,15 +12,19 @@ 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.BlockNode;
|
||||
import jadx.core.dex.nodes.Edge;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.trycatch.CatchAttr;
|
||||
import jadx.core.dex.trycatch.ExceptionHandler;
|
||||
import jadx.core.dex.trycatch.SplitterBlockAttr;
|
||||
import jadx.core.utils.BlockUtils;
|
||||
import jadx.core.utils.EmptyBitSet;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.BitSet;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
@@ -38,6 +42,8 @@ public class BlockMakerVisitor extends AbstractVisitor {
|
||||
InsnType.MONITOR_ENTER,
|
||||
InsnType.MONITOR_EXIT);
|
||||
|
||||
private static final BitSet EMPTY_BITSET = new EmptyBitSet();
|
||||
|
||||
private static int nextBlockId;
|
||||
|
||||
@Override
|
||||
@@ -47,6 +53,7 @@ public class BlockMakerVisitor extends AbstractVisitor {
|
||||
}
|
||||
mth.initBasicBlocks();
|
||||
makeBasicBlocks(mth);
|
||||
processBlocksTree(mth);
|
||||
BlockProcessingHelper.visit(mth);
|
||||
mth.finishBasicBlocks();
|
||||
}
|
||||
@@ -173,6 +180,9 @@ public class BlockMakerVisitor extends AbstractVisitor {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void processBlocksTree(MethodNode mth) {
|
||||
computeDominators(mth);
|
||||
markReturnBlocks(mth);
|
||||
|
||||
@@ -189,7 +199,6 @@ public class BlockMakerVisitor extends AbstractVisitor {
|
||||
throw new AssertionError("Can't fix method cfg: " + mth);
|
||||
}
|
||||
}
|
||||
|
||||
registerLoops(mth);
|
||||
}
|
||||
|
||||
@@ -220,7 +229,7 @@ public class BlockMakerVisitor extends AbstractVisitor {
|
||||
}
|
||||
|
||||
private static void computeDominators(MethodNode mth) {
|
||||
List<BlockNode> basicBlocks = mth.getBasicBlocks();
|
||||
List<BlockNode> basicBlocks = Collections.unmodifiableList(mth.getBasicBlocks());
|
||||
int nBlocks = basicBlocks.size();
|
||||
for (int i = 0; i < nBlocks; i++) {
|
||||
BlockNode block = basicBlocks.get(i);
|
||||
@@ -242,13 +251,15 @@ public class BlockMakerVisitor extends AbstractVisitor {
|
||||
continue;
|
||||
}
|
||||
BitSet d = block.getDoms();
|
||||
dset.clear();
|
||||
dset.or(d);
|
||||
if (!changed) {
|
||||
dset.clear();
|
||||
dset.or(d);
|
||||
}
|
||||
for (BlockNode pred : block.getPredecessors()) {
|
||||
d.and(pred.getDoms());
|
||||
}
|
||||
d.set(block.getId());
|
||||
if (!d.equals(dset)) {
|
||||
if (!changed && !d.equals(dset)) {
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
@@ -290,6 +301,66 @@ public class BlockMakerVisitor extends AbstractVisitor {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
computeDominanceFrontier(mth);
|
||||
}
|
||||
|
||||
private static void computeDominanceFrontier(MethodNode mth) {
|
||||
for (BlockNode exit : mth.getExitBlocks()) {
|
||||
exit.setDomFrontier(EMPTY_BITSET);
|
||||
}
|
||||
for (BlockNode block : mth.getBasicBlocks()) {
|
||||
computeBlockDF(block);
|
||||
}
|
||||
}
|
||||
|
||||
private static void computeBlockDF(BlockNode block) {
|
||||
if (block.getDomFrontier() != null) {
|
||||
return;
|
||||
}
|
||||
BitSet domFrontier = null;
|
||||
BitSet doms = block.getDoms();
|
||||
int id = block.getId();
|
||||
|
||||
for (BlockNode s : block.getSuccessors()) {
|
||||
if (s.getIDom() != block) {
|
||||
if (domFrontier == null) {
|
||||
domFrontier = new BitSet();
|
||||
}
|
||||
domFrontier.set(s.getId());
|
||||
}
|
||||
}
|
||||
for (BlockNode node : block.getDominatesOn()) {
|
||||
if (node.getIDom() == block) {
|
||||
BitSet frontier = node.getDomFrontier();
|
||||
if (frontier == null) {
|
||||
computeBlockDF(node);
|
||||
frontier = node.getDomFrontier();
|
||||
}
|
||||
for (int w = frontier.nextSetBit(0); w >= 0; w = frontier.nextSetBit(w + 1)) {
|
||||
if (id == w || !doms.get(w)) {
|
||||
if (domFrontier == null) {
|
||||
domFrontier = new BitSet();
|
||||
}
|
||||
domFrontier.set(w);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (domFrontier == null || domFrontier.cardinality() == 0) {
|
||||
domFrontier = EMPTY_BITSET;
|
||||
}
|
||||
block.setDomFrontier(domFrontier);
|
||||
}
|
||||
|
||||
private static void markReturnBlocks(MethodNode mth) {
|
||||
mth.getExitBlocks().clear();
|
||||
for (BlockNode block : mth.getBasicBlocks()) {
|
||||
if (BlockUtils.lastInsnType(block, InsnType.RETURN)) {
|
||||
block.getAttributes().add(AttributeFlag.RETURN);
|
||||
mth.getExitBlocks().add(block);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void markLoops(MethodNode mth) {
|
||||
@@ -309,19 +380,6 @@ public class BlockMakerVisitor extends AbstractVisitor {
|
||||
}
|
||||
}
|
||||
|
||||
private static void markReturnBlocks(MethodNode mth) {
|
||||
mth.getExitBlocks().clear();
|
||||
for (BlockNode block : mth.getBasicBlocks()) {
|
||||
List<InsnNode> insns = block.getInstructions();
|
||||
if (insns.size() == 1) {
|
||||
if (insns.get(0).getType() == InsnType.RETURN) {
|
||||
block.getAttributes().add(AttributeFlag.RETURN);
|
||||
mth.getExitBlocks().add(block);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void registerLoops(MethodNode mth) {
|
||||
for (BlockNode block : mth.getBasicBlocks()) {
|
||||
AttributesList attributes = block.getAttributes();
|
||||
@@ -352,6 +410,7 @@ public class BlockMakerVisitor extends AbstractVisitor {
|
||||
if (oneHeader) {
|
||||
// several back edges connected to one loop header => make additional block
|
||||
BlockNode newLoopHeader = startNewBlock(mth, block.getStartOffset());
|
||||
newLoopHeader.getAttributes().add(AttributeFlag.SYNTHETIC);
|
||||
connect(newLoopHeader, block);
|
||||
for (IAttribute a : loops) {
|
||||
LoopAttr la = (LoopAttr) a;
|
||||
@@ -362,6 +421,24 @@ public class BlockMakerVisitor extends AbstractVisitor {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// insert additional blocks if loop has several exits
|
||||
if (loops.size() == 1) {
|
||||
LoopAttr loop = (LoopAttr) loops.get(0);
|
||||
List<Edge> edges = loop.getExitEdges();
|
||||
if (edges.size() > 1) {
|
||||
boolean change = false;
|
||||
for (Edge edge : edges) {
|
||||
BlockNode target = edge.getTarget();
|
||||
if (!target.getAttributes().contains(AttributeFlag.SYNTHETIC)) {
|
||||
insertBlockBetween(mth, edge.getSource(), target);
|
||||
change = true;
|
||||
}
|
||||
}
|
||||
if (change) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (splitReturn(mth)) {
|
||||
return true;
|
||||
@@ -369,10 +446,18 @@ public class BlockMakerVisitor extends AbstractVisitor {
|
||||
if (mergeReturn(mth)) {
|
||||
return true;
|
||||
}
|
||||
// TODO detect ternary operator
|
||||
return false;
|
||||
}
|
||||
|
||||
private static BlockNode insertBlockBetween(MethodNode mth, BlockNode source, BlockNode target) {
|
||||
BlockNode newBlock = startNewBlock(mth, target.getStartOffset());
|
||||
newBlock.getAttributes().add(AttributeFlag.SYNTHETIC);
|
||||
removeConnection(source, target);
|
||||
connect(source, newBlock);
|
||||
connect(newBlock, target);
|
||||
return newBlock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge return blocks for void methods
|
||||
*/
|
||||
@@ -411,7 +496,6 @@ public class BlockMakerVisitor extends AbstractVisitor {
|
||||
if (mth.getExitBlocks().size() != 1) {
|
||||
return false;
|
||||
}
|
||||
boolean split = false;
|
||||
BlockNode exitBlock = mth.getExitBlocks().get(0);
|
||||
if (exitBlock.getPredecessors().size() > 1
|
||||
&& exitBlock.getInstructions().size() == 1
|
||||
@@ -419,10 +503,9 @@ public class BlockMakerVisitor extends AbstractVisitor {
|
||||
&& !exitBlock.getAttributes().contains(AttributeFlag.SYNTHETIC)) {
|
||||
InsnNode returnInsn = exitBlock.getInstructions().get(0);
|
||||
List<BlockNode> preds = new ArrayList<BlockNode>(exitBlock.getPredecessors());
|
||||
if (returnInsn.getArgsCount() != 0 && !isReturnArgAssignInPred(mth, preds, returnInsn)) {
|
||||
if (returnInsn.getArgsCount() != 0 && !isReturnArgAssignInPred(preds, returnInsn)) {
|
||||
return false;
|
||||
}
|
||||
split = true;
|
||||
for (BlockNode pred : preds) {
|
||||
BlockNode newRetBlock = startNewBlock(mth, exitBlock.getStartOffset());
|
||||
newRetBlock.getAttributes().add(AttributeFlag.SYNTHETIC);
|
||||
@@ -430,14 +513,13 @@ public class BlockMakerVisitor extends AbstractVisitor {
|
||||
removeConnection(pred, exitBlock);
|
||||
connect(pred, newRetBlock);
|
||||
}
|
||||
}
|
||||
if (split) {
|
||||
cleanExitNodes(mth);
|
||||
return true;
|
||||
}
|
||||
return split;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean isReturnArgAssignInPred(MethodNode mth, List<BlockNode> preds, InsnNode returnInsn) {
|
||||
private static boolean isReturnArgAssignInPred(List<BlockNode> preds, InsnNode returnInsn) {
|
||||
RegisterArg arg = (RegisterArg) returnInsn.getArg(0);
|
||||
int regNum = arg.getRegNum();
|
||||
for (BlockNode pred : preds) {
|
||||
@@ -468,6 +550,7 @@ public class BlockMakerVisitor extends AbstractVisitor {
|
||||
insn.addArg(InsnArg.reg(arg.getRegNum(), arg.getType()));
|
||||
}
|
||||
insn.getAttributes().addAll(returnInsn.getAttributes());
|
||||
insn.setOffset(returnInsn.getOffset());
|
||||
return insn;
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import jadx.core.dex.trycatch.ExcHandlerAttr;
|
||||
import jadx.core.dex.trycatch.ExceptionHandler;
|
||||
import jadx.core.dex.trycatch.TryCatchBlock;
|
||||
import jadx.core.utils.BlockUtils;
|
||||
import jadx.core.utils.InstructionRemover;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -23,7 +24,6 @@ public class BlockProcessingHelper {
|
||||
if (mth.isNoCode()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (BlockNode block : mth.getBasicBlocks()) {
|
||||
markExceptionHandlers(block);
|
||||
}
|
||||
@@ -77,7 +77,6 @@ public class BlockProcessingHelper {
|
||||
for (BlockNode node : BlockUtils.collectBlocksDominatedBy(block, block)) {
|
||||
excHandler.addBlock(node);
|
||||
}
|
||||
|
||||
for (BlockNode excBlock : excHandler.getBlocks()) {
|
||||
// remove 'monitor-exit' from exception handler blocks
|
||||
InstructionRemover remover = new InstructionRemover(excBlock.getInstructions());
|
||||
@@ -85,7 +84,6 @@ public class BlockProcessingHelper {
|
||||
if (insn.getType() == InsnType.MONITOR_ENTER) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (insn.getType() == InsnType.MONITOR_EXIT) {
|
||||
remover.add(insn);
|
||||
}
|
||||
@@ -118,7 +116,6 @@ public class BlockProcessingHelper {
|
||||
if (catchAttr == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (commonCatchAttr == null) {
|
||||
commonCatchAttr = catchAttr;
|
||||
} else if (commonCatchAttr != catchAttr) {
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package jadx.core.dex.visitors;
|
||||
|
||||
import jadx.core.dex.attributes.AttributeFlag;
|
||||
import jadx.core.dex.attributes.AttributeType;
|
||||
import jadx.core.dex.attributes.AttributesList;
|
||||
import jadx.core.dex.attributes.FieldReplaceAttr;
|
||||
import jadx.core.dex.info.AccessInfo;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.info.FieldInfo;
|
||||
@@ -15,9 +18,9 @@ import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.utils.InstructionRemover;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
public class ClassModifier extends AbstractVisitor {
|
||||
@@ -36,6 +39,8 @@ public class ClassModifier extends AbstractVisitor {
|
||||
removeSyntheticFields(cls);
|
||||
removeSyntheticMethods(cls);
|
||||
removeEmptyMethods(cls);
|
||||
|
||||
checkFieldsInit(cls);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -47,24 +52,27 @@ public class ClassModifier extends AbstractVisitor {
|
||||
for (FieldNode field : cls.getFields()) {
|
||||
if (field.getAccessFlags().isSynthetic() && field.getType().isObject()) {
|
||||
ClassNode fieldsCls = cls.dex().resolveClass(ClassInfo.fromType(field.getType()));
|
||||
ClassInfo parentClass = cls.getClassInfo().getParentClass();
|
||||
if (fieldsCls != null
|
||||
&& cls.getClassInfo().getParentClass().equals(fieldsCls.getClassInfo())) {
|
||||
&& parentClass.equals(fieldsCls.getClassInfo())) {
|
||||
int found = 0;
|
||||
for (MethodNode mth : cls.getMethods()) {
|
||||
if (removeFieldUsage(field, fieldsCls, mth)) {
|
||||
if (removeFieldUsageFromConstructor(mth, field, fieldsCls)) {
|
||||
found++;
|
||||
}
|
||||
}
|
||||
if (found != 0) {
|
||||
// TODO: make new flag for skip field generation and usage
|
||||
field.getAttributes().add(AttributeFlag.DONT_GENERATE);
|
||||
AttributesList attributes = field.getAttributes();
|
||||
FieldInfo replace = new FieldInfo(parentClass, "this", parentClass.getType());
|
||||
attributes.add(new FieldReplaceAttr(replace, true));
|
||||
attributes.add(AttributeFlag.DONT_GENERATE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean removeFieldUsage(FieldNode field, ClassNode fieldsCls, MethodNode mth) {
|
||||
private static boolean removeFieldUsageFromConstructor(MethodNode mth, FieldNode field, ClassNode fieldsCls) {
|
||||
if (!mth.getAccessFlags().isConstructor()) {
|
||||
return false;
|
||||
}
|
||||
@@ -105,18 +113,13 @@ public class ClassModifier extends AbstractVisitor {
|
||||
}
|
||||
|
||||
private static void removeSyntheticMethods(ClassNode cls) {
|
||||
for (Iterator<MethodNode> it = cls.getMethods().iterator(); it.hasNext(); ) {
|
||||
MethodNode mth = it.next();
|
||||
for (MethodNode mth : cls.getMethods()) {
|
||||
AccessInfo af = mth.getAccessFlags();
|
||||
|
||||
// remove bridge methods
|
||||
if (af.isBridge() && af.isSynthetic()) {
|
||||
if (!isMethodUniq(cls, mth)) {
|
||||
// TODO add more checks before method deletion
|
||||
it.remove();
|
||||
}
|
||||
if (af.isBridge() && af.isSynthetic() && !isMethodUniq(cls, mth)) {
|
||||
// TODO add more checks before method deletion
|
||||
mth.getAttributes().add(AttributeFlag.DONT_GENERATE);
|
||||
}
|
||||
|
||||
// remove synthetic constructor for inner non-static classes
|
||||
if (af.isSynthetic() && af.isConstructor() && mth.getBasicBlocks().size() == 2) {
|
||||
List<InsnNode> insns = mth.getBasicBlocks().get(0).getInstructions();
|
||||
@@ -134,11 +137,11 @@ public class ClassModifier extends AbstractVisitor {
|
||||
private static boolean isMethodUniq(ClassNode cls, MethodNode mth) {
|
||||
MethodInfo mi = mth.getMethodInfo();
|
||||
for (MethodNode otherMth : cls.getMethods()) {
|
||||
MethodInfo omi = otherMth.getMethodInfo();
|
||||
if (omi.getName().equals(mi.getName())
|
||||
&& otherMth != mth) {
|
||||
if (omi.getArgumentsTypes().size() == mi.getArgumentsTypes().size()) {
|
||||
// TODO: check to args objects types
|
||||
if (otherMth != mth) {
|
||||
MethodInfo omi = otherMth.getMethodInfo();
|
||||
if (omi.getName().equals(mi.getName())
|
||||
&& omi.getArgumentsTypes().size() == mi.getArgumentsTypes().size()) {
|
||||
// TODO: check objects types
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -155,7 +158,7 @@ public class ClassModifier extends AbstractVisitor {
|
||||
&& af.isPublic()
|
||||
&& mth.getArguments(false).isEmpty()) {
|
||||
List<BlockNode> bb = mth.getBasicBlocks();
|
||||
if (bb.isEmpty() || allBlocksEmpty(bb)) {
|
||||
if (bb == null || bb.isEmpty() || allBlocksEmpty(bb)) {
|
||||
mth.getAttributes().add(AttributeFlag.DONT_GENERATE);
|
||||
}
|
||||
}
|
||||
@@ -170,4 +173,36 @@ public class ClassModifier extends AbstractVisitor {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void checkFieldsInit(ClassNode cls) {
|
||||
MethodNode clinit = cls.searchMethodByName("<clinit>()V");
|
||||
if (clinit == null
|
||||
|| !clinit.getAccessFlags().isStatic()
|
||||
|| clinit.isNoCode()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (BlockNode block : clinit.getBasicBlocks()) {
|
||||
for (InsnNode insn : block.getInstructions()) {
|
||||
if (insn.getType() == InsnType.SPUT) {
|
||||
processStaticFieldAssign(cls, (IndexInsnNode) insn);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove field initialization if it assign in "<clinit>" method
|
||||
*/
|
||||
private static void processStaticFieldAssign(ClassNode cls, IndexInsnNode insn) {
|
||||
FieldInfo field = (FieldInfo) insn.getIndex();
|
||||
String thisClass = cls.getFullName();
|
||||
if (field.getDeclClass().getFullName().equals(thisClass)) {
|
||||
FieldNode fn = cls.searchField(field);
|
||||
if (fn != null && fn.getAccessFlags().isFinal()) {
|
||||
fn.getAttributes().remove(AttributeType.FIELD_VALUE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.InsnWrapArg;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.instructions.mods.ConstructorInsn;
|
||||
import jadx.core.dex.instructions.mods.TernaryInsn;
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
@@ -17,16 +18,16 @@ import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import java.util.Set;
|
||||
|
||||
public class CodeShrinker extends AbstractVisitor {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(CodeShrinker.class);
|
||||
|
||||
@Override
|
||||
public void visit(MethodNode mth) {
|
||||
shrinkMethod(mth);
|
||||
}
|
||||
|
||||
public static void shrinkMethod(MethodNode mth) {
|
||||
if (mth.isNoCode() || mth.getAttributes().contains(AttributeFlag.DONT_SHRINK)) {
|
||||
return;
|
||||
}
|
||||
@@ -48,13 +49,20 @@ public class CodeShrinker extends AbstractVisitor {
|
||||
this.argsList = argsList;
|
||||
this.pos = pos;
|
||||
this.inlineBorder = pos;
|
||||
this.args = new LinkedList<RegisterArg>();
|
||||
this.args = getArgs(insn);
|
||||
}
|
||||
|
||||
public static List<RegisterArg> getArgs(InsnNode insn) {
|
||||
LinkedList<RegisterArg> args = new LinkedList<RegisterArg>();
|
||||
addArgs(insn, args);
|
||||
return args;
|
||||
}
|
||||
|
||||
private static void addArgs(InsnNode insn, List<RegisterArg> args) {
|
||||
if (insn.getType() == InsnType.CONSTRUCTOR) {
|
||||
args.add(((ConstructorInsn) insn).getInstanceArg());
|
||||
} else if (insn.getType() == InsnType.TERNARY) {
|
||||
args.addAll(((TernaryInsn) insn).getCondition().getRegisterArgs());
|
||||
}
|
||||
for (InsnArg arg : insn.getArguments()) {
|
||||
if (arg.isRegister()) {
|
||||
@@ -85,26 +93,32 @@ public class CodeShrinker extends AbstractVisitor {
|
||||
}
|
||||
|
||||
private boolean canMove(int from, int to) {
|
||||
from++;
|
||||
if (from == to) {
|
||||
List<RegisterArg> movedArgs = argsList.get(from).getArgs();
|
||||
int start = from + 1;
|
||||
if (start == to) {
|
||||
// previous instruction or on edge of inline border
|
||||
return true;
|
||||
}
|
||||
if (from > to) {
|
||||
throw new JadxRuntimeException("Invalid inline insn positions: " + from + " - " + to);
|
||||
if (start > to) {
|
||||
throw new JadxRuntimeException("Invalid inline insn positions: " + start + " - " + to);
|
||||
}
|
||||
for (int i = from; i < to - 1; i++) {
|
||||
for (int i = start; i < to; i++) {
|
||||
ArgsInfo argsInfo = argsList.get(i);
|
||||
if (argsInfo.getInlinedInsn() == this) {
|
||||
continue;
|
||||
}
|
||||
if (!argsInfo.insn.canReorder()) {
|
||||
InsnNode curInsn = argsInfo.insn;
|
||||
if (!curInsn.canReorder() || usedArgAssign(curInsn, movedArgs)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean usedArgAssign(InsnNode insn, List<RegisterArg> args) {
|
||||
return insn.getResult() != null && args.contains(insn.getResult());
|
||||
}
|
||||
|
||||
public WrapInfo inline(int assignInsnPos, RegisterArg arg) {
|
||||
ArgsInfo argsInfo = argsList.get(assignInsnPos);
|
||||
argsInfo.inlinedInsn = this;
|
||||
@@ -152,7 +166,10 @@ public class CodeShrinker extends AbstractVisitor {
|
||||
}
|
||||
}
|
||||
|
||||
private void shrinkBlock(MethodNode mth, BlockNode block) {
|
||||
private static void shrinkBlock(MethodNode mth, BlockNode block) {
|
||||
if (block.getInstructions().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
InsnList insnList = new InsnList(block.getInstructions());
|
||||
int insnCount = insnList.size();
|
||||
List<ArgsInfo> argsList = new ArrayList<ArgsInfo>(insnCount);
|
||||
@@ -186,62 +203,63 @@ public class CodeShrinker extends AbstractVisitor {
|
||||
}
|
||||
} else {
|
||||
// another block
|
||||
if (block.getPredecessors().size() == 1) {
|
||||
BlockNode assignBlock = BlockUtils.getBlockByInsn(mth, assignInsn);
|
||||
if (canMoveBetweenBlocks(assignInsn, assignBlock, block, argsInfo.getInsn())) {
|
||||
arg.wrapInstruction(assignInsn);
|
||||
InsnList.remove(assignBlock, assignInsn);
|
||||
}
|
||||
BlockNode assignBlock = BlockUtils.getBlockByInsn(mth, assignInsn);
|
||||
if (assignBlock != null
|
||||
&& canMoveBetweenBlocks(assignInsn, assignBlock, block, argsInfo.getInsn())) {
|
||||
arg.wrapInstruction(assignInsn);
|
||||
InsnList.remove(assignBlock, assignInsn);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (WrapInfo wrapInfo : wrapList) {
|
||||
wrapInfo.getArg().wrapInstruction(wrapInfo.getInsn());
|
||||
if (!wrapList.isEmpty()) {
|
||||
for (WrapInfo wrapInfo : wrapList) {
|
||||
wrapInfo.getArg().wrapInstruction(wrapInfo.getInsn());
|
||||
}
|
||||
for (WrapInfo wrapInfo : wrapList) {
|
||||
insnList.remove(wrapInfo.getInsn());
|
||||
}
|
||||
}
|
||||
for (WrapInfo wrapInfo : wrapList) {
|
||||
insnList.remove(wrapInfo.getInsn());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private boolean canMoveBetweenBlocks(InsnNode assignInsn, BlockNode assignBlock,
|
||||
BlockNode useBlock, InsnNode useInsn) {
|
||||
if (!useBlock.getPredecessors().contains(assignBlock)
|
||||
&& !BlockUtils.isOnlyOnePathExists(assignBlock, useBlock)) {
|
||||
private static boolean canMoveBetweenBlocks(InsnNode assignInsn, BlockNode assignBlock,
|
||||
BlockNode useBlock, InsnNode useInsn) {
|
||||
if (!BlockUtils.isPathExists(assignBlock, useBlock)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
List<RegisterArg> args = ArgsInfo.getArgs(assignInsn);
|
||||
boolean startCheck = false;
|
||||
for (InsnNode insn : assignBlock.getInstructions()) {
|
||||
if (startCheck) {
|
||||
if (!insn.canReorder()) {
|
||||
return false;
|
||||
}
|
||||
if (startCheck && (!insn.canReorder() || ArgsInfo.usedArgAssign(insn, args))) {
|
||||
return false;
|
||||
}
|
||||
if (insn == assignInsn) {
|
||||
startCheck = true;
|
||||
}
|
||||
}
|
||||
BlockNode next = assignBlock.getCleanSuccessors().get(0);
|
||||
while (next != useBlock) {
|
||||
for (InsnNode insn : assignBlock.getInstructions()) {
|
||||
if (!insn.canReorder()) {
|
||||
Set<BlockNode> pathsBlocks = BlockUtils.getAllPathsBlocks(assignBlock, useBlock);
|
||||
pathsBlocks.remove(assignBlock);
|
||||
pathsBlocks.remove(useBlock);
|
||||
for (BlockNode block : pathsBlocks) {
|
||||
for (InsnNode insn : block.getInstructions()) {
|
||||
if (!insn.canReorder() || ArgsInfo.usedArgAssign(insn, args)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
next = next.getCleanSuccessors().get(0);
|
||||
}
|
||||
for (InsnNode insn : useBlock.getInstructions()) {
|
||||
if (insn == useInsn) {
|
||||
return true;
|
||||
}
|
||||
if (!insn.canReorder()) {
|
||||
if (!insn.canReorder() || ArgsInfo.usedArgAssign(insn, args)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
throw new JadxRuntimeException("Can't process instruction move : " + assignBlock);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public static InsnArg inlineArgument(MethodNode mth, RegisterArg arg) {
|
||||
InsnNode assignInsn = arg.getAssignInsn();
|
||||
if (assignInsn == null) {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package jadx.core.dex.visitors;
|
||||
|
||||
import jadx.core.dex.info.FieldInfo;
|
||||
import jadx.core.dex.instructions.IfNode;
|
||||
import jadx.core.dex.instructions.IndexInsnNode;
|
||||
import jadx.core.dex.instructions.InsnType;
|
||||
import jadx.core.dex.instructions.InvokeNode;
|
||||
@@ -12,9 +11,9 @@ import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.utils.BlockUtils;
|
||||
import jadx.core.utils.InstructionRemover;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -22,15 +21,17 @@ public class ConstInlinerVisitor extends AbstractVisitor {
|
||||
|
||||
@Override
|
||||
public void visit(MethodNode mth) throws JadxException {
|
||||
if (mth.isNoCode())
|
||||
if (mth.isNoCode()) {
|
||||
return;
|
||||
|
||||
}
|
||||
for (BlockNode block : mth.getBasicBlocks()) {
|
||||
for (Iterator<InsnNode> it = block.getInstructions().iterator(); it.hasNext(); ) {
|
||||
InsnNode insn = it.next();
|
||||
if (checkInsn(mth, block, insn))
|
||||
it.remove();
|
||||
InstructionRemover remover = new InstructionRemover(block.getInstructions());
|
||||
for (InsnNode insn : block.getInstructions()) {
|
||||
if (checkInsn(mth, block, insn)) {
|
||||
remover.add(insn);
|
||||
}
|
||||
}
|
||||
remover.perform();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,49 +47,54 @@ public class ConstInlinerVisitor extends AbstractVisitor {
|
||||
long lit = ((LiteralArg) arg).getLiteral();
|
||||
return replaceConst(mth, block, insn, lit);
|
||||
}
|
||||
// TODO process string and class const
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean replaceConst(MethodNode mth, BlockNode block, InsnNode insn, long literal) {
|
||||
List<InsnArg> use = insn.getResult().getTypedVar().getUseList();
|
||||
|
||||
int replace = 0;
|
||||
int replaceCount = 0;
|
||||
int assignCount = 0;
|
||||
for (InsnArg arg : use) {
|
||||
InsnNode useInsn = arg.getParentInsn();
|
||||
if (useInsn == null)
|
||||
if (arg == insn.getResult() || useInsn == null) {
|
||||
assignCount++;
|
||||
continue;
|
||||
|
||||
}
|
||||
BlockNode useBlock = BlockUtils.getBlockByInsn(mth, useInsn);
|
||||
if (useBlock == block || useBlock.isDominator(block)) {
|
||||
if (arg != insn.getResult() && !registerReassignOnPath(block, useBlock, insn)) {
|
||||
boolean isDominator = useBlock == block || useBlock.isDominator(block);
|
||||
if (isDominator && !registerReassignOnPath(block, useBlock, insn)) {
|
||||
LiteralArg litArg;
|
||||
if (use.size() == 2) {
|
||||
// arg used only in one place
|
||||
litArg = InsnArg.lit(literal, arg.getType());
|
||||
} else {
|
||||
// in most cases type not equal arg.getType()
|
||||
// just set unknown type and run type fixer
|
||||
LiteralArg litArg = InsnArg.lit(literal, ArgType.UNKNOWN);
|
||||
if (useInsn.replaceArg(arg, litArg)) {
|
||||
fixTypes(mth, useInsn, litArg);
|
||||
replace++;
|
||||
}
|
||||
litArg = InsnArg.lit(literal, ArgType.UNKNOWN);
|
||||
}
|
||||
if (useInsn.replaceArg(arg, litArg)) {
|
||||
fixTypes(mth, useInsn, litArg);
|
||||
replaceCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return (replace + 1) == use.size();
|
||||
return replaceCount == use.size() - assignCount;
|
||||
}
|
||||
|
||||
private static boolean registerReassignOnPath(BlockNode block, BlockNode useBlock, InsnNode assignInsn) {
|
||||
if (block == useBlock)
|
||||
if (block == useBlock) {
|
||||
return false;
|
||||
|
||||
}
|
||||
Set<BlockNode> blocks = BlockUtils.getAllPathsBlocks(block, useBlock);
|
||||
// TODO store list of assign insn for each register
|
||||
int regNum = assignInsn.getResult().getRegNum();
|
||||
for (BlockNode b : blocks) {
|
||||
for (InsnNode insn : b.getInstructions()) {
|
||||
if (insn.getResult() != null
|
||||
&& insn != assignInsn
|
||||
&& insn.getResult().getRegNum() == regNum)
|
||||
&& insn.getResult().getRegNum() == regNum) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
@@ -110,27 +116,12 @@ public class ConstInlinerVisitor extends AbstractVisitor {
|
||||
break;
|
||||
|
||||
case IPUT:
|
||||
case SPUT: {
|
||||
case SPUT:
|
||||
IndexInsnNode node = (IndexInsnNode) insn;
|
||||
insn.getArg(0).merge(((FieldInfo) node.getIndex()).getType());
|
||||
break;
|
||||
}
|
||||
|
||||
case IF: {
|
||||
IfNode ifnode = (IfNode) insn;
|
||||
if (!ifnode.isZeroCmp()) {
|
||||
InsnArg arg0 = insn.getArg(0);
|
||||
InsnArg arg1 = insn.getArg(1);
|
||||
if (arg0 == litArg) {
|
||||
arg0.merge(arg1);
|
||||
} else {
|
||||
arg1.merge(arg0);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CMP_G:
|
||||
case CMP_L: {
|
||||
InsnArg arg0 = insn.getArg(0);
|
||||
InsnArg arg1 = insn.getArg(1);
|
||||
if (arg0 == litArg) {
|
||||
@@ -140,6 +131,16 @@ public class ConstInlinerVisitor extends AbstractVisitor {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CMP_G:
|
||||
case CMP_L:
|
||||
InsnArg arg0 = insn.getArg(0);
|
||||
InsnArg arg1 = insn.getArg(1);
|
||||
if (arg0 == litArg) {
|
||||
arg0.merge(arg1);
|
||||
} else {
|
||||
arg1.merge(arg0);
|
||||
}
|
||||
break;
|
||||
|
||||
case RETURN:
|
||||
if (insn.getArgsCount() != 0) {
|
||||
@@ -156,10 +157,11 @@ public class ConstInlinerVisitor extends AbstractVisitor {
|
||||
InsnArg arg = insn.getArg(i);
|
||||
if (!arg.getType().isTypeKnown()) {
|
||||
ArgType type;
|
||||
if (k >= 0)
|
||||
if (k >= 0) {
|
||||
type = types.get(k);
|
||||
else
|
||||
} else {
|
||||
type = mth.getParentClass().getClassInfo().getType();
|
||||
}
|
||||
arg.merge(type);
|
||||
}
|
||||
k++;
|
||||
|
||||
+5
-3
@@ -4,15 +4,17 @@ import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.utils.ErrorsCounter;
|
||||
|
||||
public class DepthTraverser {
|
||||
public class DepthTraversal {
|
||||
|
||||
public static void visit(IDexTreeVisitor visitor, ClassNode cls) {
|
||||
try {
|
||||
if (visitor.visit(cls)) {
|
||||
for (ClassNode inCls : cls.getInnerClasses())
|
||||
for (ClassNode inCls : cls.getInnerClasses()) {
|
||||
visit(visitor, inCls);
|
||||
for (MethodNode mth : cls.getMethods())
|
||||
}
|
||||
for (MethodNode mth : cls.getMethods()) {
|
||||
visit(visitor, mth);
|
||||
}
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
ErrorsCounter.classError(cls,
|
||||
@@ -3,6 +3,8 @@ package jadx.core.dex.visitors;
|
||||
import jadx.core.codegen.CodeWriter;
|
||||
import jadx.core.codegen.MethodGen;
|
||||
import jadx.core.dex.attributes.IAttributeNode;
|
||||
import jadx.core.dex.instructions.IfNode;
|
||||
import jadx.core.dex.instructions.InsnType;
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.dex.nodes.IContainer;
|
||||
import jadx.core.dex.nodes.IRegion;
|
||||
@@ -11,14 +13,18 @@ import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.trycatch.ExceptionHandler;
|
||||
import jadx.core.utils.BlockUtils;
|
||||
import jadx.core.utils.InsnUtils;
|
||||
import jadx.core.utils.RegionUtils;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class DotGraphVisitor extends AbstractVisitor {
|
||||
|
||||
private static final String NL = "\\l";
|
||||
private static final boolean PRINT_REGISTERS_STATES = false;
|
||||
private static final boolean PRINT_DOMINATORS = false;
|
||||
|
||||
private final File dir;
|
||||
private final boolean useRegions;
|
||||
@@ -36,153 +42,199 @@ public class DotGraphVisitor extends AbstractVisitor {
|
||||
|
||||
@Override
|
||||
public void visit(MethodNode mth) {
|
||||
if (mth.isNoCode())
|
||||
if (mth.isNoCode()) {
|
||||
return;
|
||||
|
||||
CodeWriter dot = new CodeWriter();
|
||||
CodeWriter conn = new CodeWriter();
|
||||
|
||||
dot.startLine("digraph \"CFG for"
|
||||
+ escape(mth.getParentClass().getFullName() + "." + mth.getMethodInfo().getShortId())
|
||||
+ "\" {");
|
||||
|
||||
if (useRegions) {
|
||||
if (mth.getRegion() == null)
|
||||
return;
|
||||
|
||||
processRegion(mth, mth.getRegion(), dot, conn);
|
||||
if (mth.getExceptionHandlers() != null) {
|
||||
for (ExceptionHandler h : mth.getExceptionHandlers())
|
||||
if (h.getHandlerRegion() != null)
|
||||
processRegion(mth, h.getHandlerRegion(), dot, conn);
|
||||
}
|
||||
} else {
|
||||
for (BlockNode block : mth.getBasicBlocks())
|
||||
processBlock(mth, block, dot, conn);
|
||||
}
|
||||
|
||||
String attrs = attributesString(mth);
|
||||
|
||||
dot.startLine("MethodNode[shape=record,label=\"{"
|
||||
+ escape(mth.getAccessFlags().makeString())
|
||||
+ escape(mth.getReturnType() + " "
|
||||
+ mth.getParentClass().getFullName() + "." + mth.getName()
|
||||
+ "(" + Utils.listToString(mth.getArguments(true)) + ") ")
|
||||
+ (attrs.length() == 0 ? "" : " | " + attrs)
|
||||
+ "}\"];");
|
||||
|
||||
dot.startLine("MethodNode -> " + makeName(mth.getEnterBlock()) + ";");
|
||||
|
||||
dot.add(conn);
|
||||
|
||||
dot.startLine('}');
|
||||
dot.startLine();
|
||||
|
||||
String fileName = Utils.escape(mth.getMethodInfo().getShortId())
|
||||
+ (useRegions ? ".regions" : "")
|
||||
+ (rawInsn ? ".raw" : "")
|
||||
+ ".dot";
|
||||
dot.save(dir, mth.getParentClass().getClassInfo().getFullPath() + "_graphs", fileName);
|
||||
new DumpDotGraph().process(mth);
|
||||
}
|
||||
|
||||
private void processRegion(MethodNode mth, IContainer region, CodeWriter dot, CodeWriter conn) {
|
||||
if (region instanceof IRegion) {
|
||||
IRegion r = (IRegion) region;
|
||||
String attrs = attributesString(r);
|
||||
dot.startLine("subgraph " + makeName(region) + " {");
|
||||
dot.startLine("label = \"" + r.toString()
|
||||
+ (attrs.length() == 0 ? "" : " | " + attrs)
|
||||
+ "\";");
|
||||
dot.startLine("node [shape=record,color=blue];");
|
||||
private class DumpDotGraph {
|
||||
private CodeWriter dot = new CodeWriter();
|
||||
private CodeWriter conn = new CodeWriter();
|
||||
|
||||
for (IContainer c : r.getSubBlocks()) {
|
||||
processRegion(mth, c, dot, conn);
|
||||
public void process(MethodNode mth) {
|
||||
dot.startLine("digraph \"CFG for");
|
||||
dot.add(escape(mth.getParentClass().getFullName() + "." + mth.getMethodInfo().getShortId()));
|
||||
dot.add("\" {");
|
||||
|
||||
if (useRegions) {
|
||||
if (mth.getRegion() == null) {
|
||||
return;
|
||||
}
|
||||
processMethodRegion(mth);
|
||||
} else {
|
||||
for (BlockNode block : mth.getBasicBlocks()) {
|
||||
processBlock(mth, block, false);
|
||||
}
|
||||
}
|
||||
|
||||
dot.startLine("MethodNode[shape=record,label=\"{");
|
||||
dot.add(escape(mth.getAccessFlags().makeString()));
|
||||
dot.add(escape(mth.getReturnType() + " "
|
||||
+ mth.getParentClass().getFullName() + "." + mth.getName()
|
||||
+ "(" + Utils.listToString(mth.getArguments(true)) + ") "));
|
||||
|
||||
String attrs = attributesString(mth);
|
||||
if (attrs.length() != 0) {
|
||||
dot.add(" | ").add(attrs);
|
||||
}
|
||||
dot.add("}\"];");
|
||||
|
||||
dot.startLine("MethodNode -> ").add(makeName(mth.getEnterBlock())).add(';');
|
||||
|
||||
dot.add(conn.toString());
|
||||
|
||||
dot.startLine('}');
|
||||
} else if (region instanceof BlockNode) {
|
||||
processBlock(mth, (BlockNode) region, dot, conn);
|
||||
}
|
||||
}
|
||||
dot.startLine();
|
||||
|
||||
private void processBlock(MethodNode mth, BlockNode block, CodeWriter dot, CodeWriter conn) {
|
||||
String attrs = attributesString(block);
|
||||
if (PRINT_REGISTERS_STATES) {
|
||||
if (block.getStartState() != null) {
|
||||
if (attrs.length() != 0)
|
||||
attrs += "|";
|
||||
attrs += escape("RS: " + block.getStartState()) + NL;
|
||||
attrs += escape("RE: " + block.getEndState()) + NL;
|
||||
String fileName = Utils.escape(mth.getMethodInfo().getShortId())
|
||||
+ (useRegions ? ".regions" : "")
|
||||
+ (rawInsn ? ".raw" : "")
|
||||
+ ".dot";
|
||||
dot.save(dir, mth.getParentClass().getClassInfo().getFullPath() + "_graphs", fileName);
|
||||
}
|
||||
|
||||
private void processMethodRegion(MethodNode mth) {
|
||||
processRegion(mth, mth.getRegion());
|
||||
for (ExceptionHandler h : mth.getExceptionHandlers()) {
|
||||
if (h.getHandlerRegion() != null) {
|
||||
processRegion(mth, h.getHandlerRegion());
|
||||
}
|
||||
}
|
||||
Set<BlockNode> regionsBlocks = new HashSet<BlockNode>(mth.getBasicBlocks().size());
|
||||
RegionUtils.getAllRegionBlocks(mth.getRegion(), regionsBlocks);
|
||||
for (ExceptionHandler handler : mth.getExceptionHandlers()) {
|
||||
IContainer handlerRegion = handler.getHandlerRegion();
|
||||
if (handlerRegion != null) {
|
||||
RegionUtils.getAllRegionBlocks(handlerRegion, regionsBlocks);
|
||||
}
|
||||
}
|
||||
for (BlockNode block : mth.getBasicBlocks()) {
|
||||
if (!regionsBlocks.contains(block)) {
|
||||
processBlock(mth, block, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String insns = insertInsns(mth, block);
|
||||
private void processRegion(MethodNode mth, IContainer region) {
|
||||
if (region instanceof IRegion) {
|
||||
IRegion r = (IRegion) region;
|
||||
dot.startLine("subgraph " + makeName(region) + " {");
|
||||
dot.startLine("label = \"").add(r);
|
||||
String attrs = attributesString(r);
|
||||
if (attrs.length() != 0) {
|
||||
dot.add(" | ").add(attrs);
|
||||
}
|
||||
dot.add("\";");
|
||||
dot.startLine("node [shape=record,color=blue];");
|
||||
|
||||
dot.startLine(makeName(block) + " [shape=record,label=\"{"
|
||||
+ block.getId() + "\\:\\ "
|
||||
+ InsnUtils.formatOffset(block.getStartOffset())
|
||||
+ (attrs.length() == 0 ? "" : "|" + attrs)
|
||||
+ (insns.length() == 0 ? "" : "|" + insns)
|
||||
+ "}\"];");
|
||||
for (IContainer c : r.getSubBlocks()) {
|
||||
processRegion(mth, c);
|
||||
}
|
||||
|
||||
for (BlockNode next : block.getSuccessors())
|
||||
conn.startLine(makeName(block) + " -> " + makeName(next) + ";");
|
||||
|
||||
for (BlockNode next : block.getDominatesOn())
|
||||
conn.startLine(makeName(block) + " -> " + makeName(next) + "[style=dotted];");
|
||||
|
||||
// add all dominators connections
|
||||
if (false) {
|
||||
for (BlockNode next : BlockUtils.bitsetToBlocks(mth, block.getDoms()))
|
||||
conn.startLine(makeName(block) + " -> " + makeName(next) + "[style=dotted, color=green];");
|
||||
}
|
||||
}
|
||||
|
||||
private String attributesString(IAttributeNode block) {
|
||||
StringBuilder attrs = new StringBuilder();
|
||||
for (String attr : block.getAttributes().getAttributeStrings()) {
|
||||
attrs.append(escape(attr)).append(NL);
|
||||
}
|
||||
return attrs.toString();
|
||||
}
|
||||
|
||||
private String makeName(IContainer c) {
|
||||
String name;
|
||||
if (c instanceof BlockNode) {
|
||||
name = "Node_" + ((BlockNode) c).getId();
|
||||
} else {
|
||||
name = "cluster_" + c.getClass().getSimpleName() + "_" + c.hashCode();
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
private String insertInsns(MethodNode mth, BlockNode block) {
|
||||
if (rawInsn) {
|
||||
StringBuilder str = new StringBuilder();
|
||||
for (InsnNode insn : block.getInstructions()) {
|
||||
str.append(escape(insn.toString() + " " + insn.getAttributes()));
|
||||
str.append(NL);
|
||||
dot.startLine('}');
|
||||
} else if (region instanceof BlockNode) {
|
||||
processBlock(mth, (BlockNode) region, false);
|
||||
}
|
||||
return str.toString();
|
||||
} else {
|
||||
CodeWriter code = new CodeWriter(0);
|
||||
MethodGen.makeFallbackInsns(code, mth, block.getInstructions(), false);
|
||||
String str = escape(code.newLine().toString());
|
||||
if (str.startsWith(NL))
|
||||
str = str.substring(NL.length());
|
||||
return str;
|
||||
}
|
||||
}
|
||||
|
||||
private String escape(String string) {
|
||||
return string
|
||||
.replace("\\", "") // TODO replace \"
|
||||
.replace("/", "\\/")
|
||||
.replace(">", "\\>").replace("<", "\\<")
|
||||
.replace("{", "\\{").replace("}", "\\}")
|
||||
.replace("\"", "\\\"")
|
||||
.replace("-", "\\-")
|
||||
.replace("|", "\\|")
|
||||
.replace("\n", NL);
|
||||
private void processBlock(MethodNode mth, BlockNode block, boolean error) {
|
||||
String attrs = attributesString(block);
|
||||
dot.startLine(makeName(block));
|
||||
dot.add(" [shape=record,");
|
||||
if (error) {
|
||||
dot.add("color=red,");
|
||||
}
|
||||
dot.add("label=\"{");
|
||||
dot.add(block.getId()).add("\\:\\ ");
|
||||
dot.add(InsnUtils.formatOffset(block.getStartOffset()));
|
||||
if (attrs.length() != 0) {
|
||||
dot.add('|').add(attrs);
|
||||
}
|
||||
String insns = insertInsns(mth, block);
|
||||
if (insns.length() != 0) {
|
||||
dot.add('|').add(insns);
|
||||
}
|
||||
dot.add("}\"];");
|
||||
|
||||
BlockNode falsePath = null;
|
||||
List<InsnNode> list = block.getInstructions();
|
||||
if (!list.isEmpty() && list.get(0).getType() == InsnType.IF) {
|
||||
falsePath = ((IfNode) list.get(0)).getElseBlock();
|
||||
}
|
||||
for (BlockNode next : block.getSuccessors()) {
|
||||
String style = next == falsePath ? "[style=dashed]" : "";
|
||||
addEdge(block, next, style);
|
||||
}
|
||||
|
||||
if (PRINT_DOMINATORS) {
|
||||
for (BlockNode dom : BlockUtils.bitSetToBlocks(mth, block.getDoms())) {
|
||||
String style = "[color=green]";
|
||||
if (dom == block.getIDom()) {
|
||||
style = "[style=dashed, color=green]";
|
||||
}
|
||||
addEdge(block, dom, style);
|
||||
}
|
||||
|
||||
for (BlockNode dom : BlockUtils.bitSetToBlocks(mth, block.getDomFrontier())) {
|
||||
addEdge(block, dom, "[color=blue]");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addEdge(BlockNode from, BlockNode to, String style) {
|
||||
conn.startLine(makeName(from)).add(" -> ").add(makeName(to));
|
||||
conn.add(style);
|
||||
conn.add(';');
|
||||
}
|
||||
|
||||
private String attributesString(IAttributeNode block) {
|
||||
StringBuilder attrs = new StringBuilder();
|
||||
for (String attr : block.getAttributes().getAttributeStrings()) {
|
||||
attrs.append(escape(attr)).append(NL);
|
||||
}
|
||||
return attrs.toString();
|
||||
}
|
||||
|
||||
private String makeName(IContainer c) {
|
||||
String name;
|
||||
if (c instanceof BlockNode) {
|
||||
name = "Node_" + ((BlockNode) c).getId();
|
||||
} else {
|
||||
name = "cluster_" + c.getClass().getSimpleName() + "_" + c.hashCode();
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
private String insertInsns(MethodNode mth, BlockNode block) {
|
||||
if (rawInsn) {
|
||||
StringBuilder str = new StringBuilder();
|
||||
for (InsnNode insn : block.getInstructions()) {
|
||||
str.append(escape(insn + " " + insn.getAttributes()));
|
||||
str.append(NL);
|
||||
}
|
||||
return str.toString();
|
||||
} else {
|
||||
CodeWriter code = new CodeWriter(0);
|
||||
MethodGen.addFallbackInsns(code, mth, block.getInstructions(), false);
|
||||
String str = escape(code.newLine().toString());
|
||||
if (str.startsWith(NL)) {
|
||||
str = str.substring(NL.length());
|
||||
}
|
||||
return str;
|
||||
}
|
||||
}
|
||||
|
||||
private String escape(String string) {
|
||||
return string
|
||||
.replace("\\", "") // TODO replace \"
|
||||
.replace("/", "\\/")
|
||||
.replace(">", "\\>").replace("<", "\\<")
|
||||
.replace("{", "\\{").replace("}", "\\}")
|
||||
.replace("\"", "\\\"")
|
||||
.replace("-", "\\-")
|
||||
.replace("|", "\\|")
|
||||
.replace("\n", NL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package jadx.core.dex.visitors;
|
||||
|
||||
import jadx.core.codegen.TypeGen;
|
||||
import jadx.core.dex.attributes.AttributeFlag;
|
||||
import jadx.core.dex.attributes.EnumClassAttr;
|
||||
import jadx.core.dex.attributes.EnumClassAttr.EnumField;
|
||||
@@ -8,6 +9,7 @@ import jadx.core.dex.info.FieldInfo;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.IndexInsnNode;
|
||||
import jadx.core.dex.instructions.InsnType;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.instructions.mods.ConstructorInsn;
|
||||
@@ -30,8 +32,9 @@ public class EnumVisitor extends AbstractVisitor {
|
||||
|
||||
@Override
|
||||
public boolean visit(ClassNode cls) throws JadxException {
|
||||
if (!cls.isEnum())
|
||||
if (!cls.isEnum()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// collect enum fields, remove synthetic
|
||||
List<FieldNode> enumFields = new ArrayList<FieldNode>();
|
||||
@@ -47,19 +50,29 @@ public class EnumVisitor extends AbstractVisitor {
|
||||
|
||||
MethodNode staticMethod = null;
|
||||
|
||||
ArgType clsType = cls.getClassInfo().getType();
|
||||
String enumConstructor = "<init>(Ljava/lang/String;I)V";
|
||||
String valuesOfMethod = "valueOf(Ljava/lang/String;)" + TypeGen.signature(clsType);
|
||||
String valuesMethod = "values()" + TypeGen.signature(ArgType.array(clsType));
|
||||
|
||||
// remove synthetic methods
|
||||
for (Iterator<MethodNode> it = cls.getMethods().iterator(); it.hasNext(); ) {
|
||||
MethodNode mth = it.next();
|
||||
MethodInfo mi = mth.getMethodInfo();
|
||||
if (mi.isClassInit()) {
|
||||
staticMethod = mth;
|
||||
} else if (mi.isConstructor() && !mth.getAccessFlags().isSynthetic()) {
|
||||
if (mi.getShortId().equals("<init>(Ljava/lang/String;I)"))
|
||||
} else {
|
||||
String shortId = mi.getShortId();
|
||||
boolean isSynthetic = mth.getAccessFlags().isSynthetic();
|
||||
if (mi.isConstructor() && !isSynthetic) {
|
||||
if (shortId.equals(enumConstructor)) {
|
||||
it.remove();
|
||||
}
|
||||
} else if (isSynthetic
|
||||
|| shortId.equals(valuesMethod)
|
||||
|| shortId.equals(valuesOfMethod)) {
|
||||
it.remove();
|
||||
} else if (mth.getAccessFlags().isSynthetic()
|
||||
|| mi.getShortId().equals("values()")
|
||||
|| mi.getShortId().equals("valueOf(Ljava/lang/String;)")) {
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,10 +102,11 @@ public class EnumVisitor extends AbstractVisitor {
|
||||
IndexInsnNode fp = (IndexInsnNode) insn;
|
||||
FieldInfo f = (FieldInfo) fp.getIndex();
|
||||
if (f.getName().equals("$VALUES")) {
|
||||
if (i == size - 1)
|
||||
if (i == size - 1) {
|
||||
cls.getMethods().remove(staticMethod);
|
||||
else
|
||||
} else {
|
||||
list.subList(0, i + 1).clear();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -102,16 +116,19 @@ public class EnumVisitor extends AbstractVisitor {
|
||||
if (insn.getType() == InsnType.CONSTRUCTOR) {
|
||||
ConstructorInsn co = (ConstructorInsn) insn;
|
||||
|
||||
if (insn.getArgsCount() < 2)
|
||||
if (insn.getArgsCount() < 2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ClassInfo clsInfo = co.getClassType();
|
||||
ClassNode constrCls = cls.dex().resolveClass(clsInfo);
|
||||
if (constrCls == null)
|
||||
if (constrCls == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!clsInfo.equals(cls.getClassInfo()) && !constrCls.getAccessFlags().isEnum())
|
||||
if (!clsInfo.equals(cls.getClassInfo()) && !constrCls.getAccessFlags().isEnum()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
RegisterArg nameArg = (RegisterArg) insn.getArg(0);
|
||||
// InsnArg pos = insn.getArg(1);
|
||||
@@ -130,8 +147,9 @@ public class EnumVisitor extends AbstractVisitor {
|
||||
constrArg = iArg;
|
||||
} else {
|
||||
constrArg = CodeShrinker.inlineArgument(staticMethod, (RegisterArg) iArg);
|
||||
if (constrArg == null)
|
||||
if (constrArg == null) {
|
||||
throw new JadxException("Can't inline constructor arg in enum: " + cls);
|
||||
}
|
||||
}
|
||||
field.getArgs().add(constrArg);
|
||||
}
|
||||
@@ -143,8 +161,9 @@ public class EnumVisitor extends AbstractVisitor {
|
||||
// remove constructor, because it is anonymous class
|
||||
for (Iterator<?> mit = innerCls.getMethods().iterator(); mit.hasNext(); ) {
|
||||
MethodNode innerMth = (MethodNode) mit.next();
|
||||
if (innerMth.getAccessFlags().isConstructor())
|
||||
if (innerMth.getAccessFlags().isConstructor()) {
|
||||
mit.remove();
|
||||
}
|
||||
}
|
||||
field.setCls(innerCls);
|
||||
innerCls.getAttributes().add(AttributeFlag.DONT_GENERATE);
|
||||
|
||||
@@ -10,9 +10,9 @@ public class FallbackModeVisitor extends AbstractVisitor {
|
||||
|
||||
@Override
|
||||
public void visit(MethodNode mth) throws JadxException {
|
||||
if (mth.isNoCode())
|
||||
if (mth.isNoCode()) {
|
||||
return;
|
||||
|
||||
}
|
||||
for (InsnNode insn : mth.getInstructions()) {
|
||||
// remove 'exception catch' for instruction which don't throw any exceptions
|
||||
CatchAttr catchAttr = (CatchAttr) insn.getAttributes().get(AttributeType.CATCH_BLOCK);
|
||||
|
||||
+11
-7
@@ -10,17 +10,21 @@ import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
|
||||
public class MethodInlinerVisitor extends AbstractVisitor {
|
||||
/**
|
||||
* Inline synthetic methods.
|
||||
*/
|
||||
public class MethodInlineVisitor extends AbstractVisitor {
|
||||
|
||||
@Override
|
||||
public void visit(MethodNode mth) throws JadxException {
|
||||
AccessInfo accessFlags = mth.getAccessFlags();
|
||||
if (accessFlags.isSynthetic() && accessFlags.isStatic()) {
|
||||
if (mth.getBasicBlocks().size() == 2) {
|
||||
BlockNode block = mth.getBasicBlocks().get(1);
|
||||
if (block.getAttributes().contains(AttributeFlag.RETURN)) {
|
||||
inlineMth(mth);
|
||||
}
|
||||
if (accessFlags.isSynthetic()
|
||||
&& accessFlags.isStatic()
|
||||
&& mth.getBasicBlocks().size() == 2) {
|
||||
BlockNode block = mth.getBasicBlocks().get(1);
|
||||
if (block.getInstructions().isEmpty()
|
||||
|| block.getAttributes().contains(AttributeFlag.RETURN)) {
|
||||
inlineMth(mth);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.trycatch.ExcHandlerAttr;
|
||||
import jadx.core.dex.trycatch.ExceptionHandler;
|
||||
import jadx.core.utils.InstructionRemover;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -50,7 +51,7 @@ public class ModVisitor extends AbstractVisitor {
|
||||
}
|
||||
}
|
||||
|
||||
private void replaceStep(MethodNode mth) {
|
||||
private static void replaceStep(MethodNode mth) {
|
||||
ClassNode parentClass = mth.getParentClass();
|
||||
for (BlockNode block : mth.getBasicBlocks()) {
|
||||
InstructionRemover remover = new InstructionRemover(block.getInstructions());
|
||||
@@ -146,7 +147,7 @@ public class ModVisitor extends AbstractVisitor {
|
||||
/**
|
||||
* Remove unnecessary instructions
|
||||
*/
|
||||
private void removeStep(MethodNode mth) {
|
||||
private static void removeStep(MethodNode mth) {
|
||||
for (BlockNode block : mth.getBasicBlocks()) {
|
||||
InstructionRemover remover = new InstructionRemover(block.getInstructions());
|
||||
|
||||
@@ -192,7 +193,7 @@ public class ModVisitor extends AbstractVisitor {
|
||||
}
|
||||
}
|
||||
|
||||
private void processExceptionHander(MethodNode mth, BlockNode block) {
|
||||
private static void processExceptionHander(MethodNode mth, BlockNode block) {
|
||||
ExcHandlerAttr handlerAttr = (ExcHandlerAttr) block.getAttributes().get(AttributeType.EXC_HANDLER);
|
||||
if (handlerAttr == null) {
|
||||
return;
|
||||
@@ -251,7 +252,7 @@ public class ModVisitor extends AbstractVisitor {
|
||||
block.getInstructions().set(i, insn);
|
||||
}
|
||||
|
||||
private void checkArgsNames(MethodNode mth) {
|
||||
private static void checkArgsNames(MethodNode mth) {
|
||||
for (RegisterArg arg : mth.getArguments(false)) {
|
||||
String name = arg.getTypedVar().getName();
|
||||
if (name != null && NameMapper.isReserved(name)) {
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
package jadx.core.dex.visitors;
|
||||
|
||||
import jadx.core.dex.attributes.AttributeFlag;
|
||||
import jadx.core.dex.instructions.ArithNode;
|
||||
import jadx.core.dex.instructions.ArithOp;
|
||||
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.RegisterArg;
|
||||
import jadx.core.dex.instructions.mods.ConstructorInsn;
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Prepare instructions for code generation pass,
|
||||
* most of this modification breaks register dependencies,
|
||||
* so this pass must be just before CodeGen.
|
||||
*/
|
||||
public class PrepareForCodeGen extends AbstractVisitor {
|
||||
|
||||
@Override
|
||||
public void visit(MethodNode mth) throws JadxException {
|
||||
List<BlockNode> blocks = mth.getBasicBlocks();
|
||||
if (blocks == null) {
|
||||
return;
|
||||
}
|
||||
for (BlockNode block : blocks) {
|
||||
removeInstructions(block);
|
||||
checkInline(block);
|
||||
removeParenthesis(block);
|
||||
modifyArith(block);
|
||||
}
|
||||
}
|
||||
|
||||
private static void removeInstructions(BlockNode block) {
|
||||
Iterator<InsnNode> it = block.getInstructions().iterator();
|
||||
while (it.hasNext()) {
|
||||
InsnNode insn = it.next();
|
||||
switch (insn.getType()) {
|
||||
case NOP:
|
||||
case MONITOR_ENTER:
|
||||
case MONITOR_EXIT:
|
||||
it.remove();
|
||||
break;
|
||||
|
||||
case CONSTRUCTOR:
|
||||
ConstructorInsn co = (ConstructorInsn) insn;
|
||||
if (co.isSelf()) {
|
||||
it.remove();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void checkInline(BlockNode block) {
|
||||
List<InsnNode> list = block.getInstructions();
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
InsnNode insn = list.get(i);
|
||||
// replace 'move' with inner wrapped instruction
|
||||
if (insn.getType() == InsnType.MOVE
|
||||
&& insn.getArg(0).isInsnWrap()
|
||||
&& !insn.getAttributes().contains(AttributeFlag.DECLARE_VAR)) {
|
||||
InsnNode wrapInsn = ((InsnWrapArg)insn.getArg(0)).getWrapInsn();
|
||||
wrapInsn.setResult(insn.getResult());
|
||||
list.set(i, wrapInsn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void removeParenthesis(BlockNode block) {
|
||||
for (InsnNode insn : block.getInstructions()) {
|
||||
checkInsn(insn);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove parenthesis for wrapped insn in arith '+' or '-'
|
||||
* ('(a + b) +c' => 'a + b + c')
|
||||
*/
|
||||
private static void checkInsn(InsnNode insn) {
|
||||
if (insn.getType() == InsnType.ARITH) {
|
||||
ArithNode arith = (ArithNode) insn;
|
||||
ArithOp op = arith.getOp();
|
||||
if (op == ArithOp.ADD || op == ArithOp.SUB) {
|
||||
for (int i = 0; i < 2; i++) {
|
||||
InsnArg arg = arith.getArg(i);
|
||||
if (arg.isInsnWrap()) {
|
||||
InsnNode wrapInsn = ((InsnWrapArg) arg).getWrapInsn();
|
||||
wrapInsn.getAttributes().add(AttributeFlag.DONT_WRAP);
|
||||
checkInsn(wrapInsn);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (InsnArg arg : insn.getArguments()) {
|
||||
if (arg.isInsnWrap()) {
|
||||
InsnNode wrapInsn = ((InsnWrapArg) arg).getWrapInsn();
|
||||
checkInsn(wrapInsn);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace arithmetic operation with short form
|
||||
* ('a = a + 2' => 'a += 2')
|
||||
*/
|
||||
private static void modifyArith(BlockNode block) {
|
||||
List<InsnNode> list = block.getInstructions();
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
InsnNode insn = list.get(i);
|
||||
if (insn.getType() == InsnType.ARITH) {
|
||||
ArithNode arith = (ArithNode) insn;
|
||||
RegisterArg res = arith.getResult();
|
||||
InsnArg arg = arith.getArg(0);
|
||||
if (res.equals(arg)) {
|
||||
ArithNode newArith = new ArithNode(arith.getOp(), res, arith.getArg(1));
|
||||
list.set(i, newArith);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -96,9 +96,9 @@ public class SimplifyVisitor extends AbstractVisitor {
|
||||
if (f.isInsnWrap()) {
|
||||
InsnNode wi = ((InsnWrapArg) f).getWrapInsn();
|
||||
if (wi.getType() == InsnType.CMP_L || wi.getType() == InsnType.CMP_G) {
|
||||
if (ifb.isZeroCmp()
|
||||
|| ((LiteralArg) ifb.getArg(1)).getLiteral() == 0) {
|
||||
ifb.changeCondition(wi.getArg(0), wi.getArg(1), ifb.getOp());
|
||||
if (ifb.getArg(1).isLiteral()
|
||||
&& ((LiteralArg) ifb.getArg(1)).getLiteral() == 0) {
|
||||
ifb.changeCondition(ifb.getOp(), wi.getArg(0), wi.getArg(1));
|
||||
} else {
|
||||
LOG.warn("TODO: cmp" + ifb);
|
||||
}
|
||||
@@ -109,7 +109,7 @@ public class SimplifyVisitor extends AbstractVisitor {
|
||||
case INVOKE:
|
||||
MethodInfo callMth = ((InvokeNode) insn).getCallMth();
|
||||
if (callMth.getDeclClass().getFullName().equals(Consts.CLASS_STRING_BUILDER)
|
||||
&& callMth.getShortId().equals("toString()")
|
||||
&& callMth.getShortId().equals(Consts.MTH_TOSTRING_SIGNATURE)
|
||||
&& insn.getArg(0).isInsnWrap()) {
|
||||
try {
|
||||
List<InsnNode> chain = flattenInsnChain(insn);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package jadx.core.dex.visitors.regions;
|
||||
|
||||
import jadx.core.Consts;
|
||||
import jadx.core.dex.attributes.AttributeFlag;
|
||||
import jadx.core.dex.attributes.AttributeType;
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.dex.nodes.IBlock;
|
||||
import jadx.core.dex.nodes.IRegion;
|
||||
@@ -22,33 +22,31 @@ public class CheckRegions extends AbstractVisitor {
|
||||
|
||||
@Override
|
||||
public void visit(MethodNode mth) throws JadxException {
|
||||
if (mth.isNoCode() || mth.getBasicBlocks().size() == 0)
|
||||
if (mth.isNoCode()
|
||||
|| mth.getBasicBlocks().isEmpty()
|
||||
|| mth.getAttributes().contains(AttributeType.JADX_ERROR)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// check if all blocks included in regions
|
||||
final Set<BlockNode> blocksInRegions = new HashSet<BlockNode>();
|
||||
IRegionVisitor collectBlocks = new AbstractRegionVisitor() {
|
||||
@Override
|
||||
public void processBlock(MethodNode mth, IBlock container) {
|
||||
if (container instanceof BlockNode)
|
||||
if (container instanceof BlockNode) {
|
||||
blocksInRegions.add((BlockNode) container);
|
||||
else
|
||||
LOG.warn("Not block node : " + container.getClass().getSimpleName());
|
||||
}
|
||||
}
|
||||
};
|
||||
DepthRegionTraverser.traverseAll(mth, collectBlocks);
|
||||
DepthRegionTraversal.traverseAll(mth, collectBlocks);
|
||||
|
||||
if (mth.getBasicBlocks().size() != blocksInRegions.size()) {
|
||||
for (BlockNode block : mth.getBasicBlocks()) {
|
||||
if (!blocksInRegions.contains(block)) {
|
||||
if (!block.getInstructions().isEmpty()
|
||||
&& !block.getAttributes().contains(AttributeFlag.SKIP)) {
|
||||
mth.getAttributes().add(AttributeFlag.INCONSISTENT_CODE);
|
||||
if (Consts.DEBUG)
|
||||
LOG.debug(" Missing block: {} in {}", block, mth);
|
||||
else
|
||||
break;
|
||||
}
|
||||
if (!blocksInRegions.contains(block)
|
||||
&& !block.getInstructions().isEmpty()
|
||||
&& !block.getAttributes().contains(AttributeFlag.SKIP)) {
|
||||
mth.getAttributes().add(AttributeFlag.INCONSISTENT_CODE);
|
||||
LOG.debug(" Missing block: {} in {}", block, mth);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -67,6 +65,6 @@ public class CheckRegions extends AbstractVisitor {
|
||||
}
|
||||
}
|
||||
};
|
||||
DepthRegionTraverser.traverseAll(mth, checkLoops);
|
||||
DepthRegionTraversal.traverseAll(mth, checkLoops);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,27 +5,28 @@ import jadx.core.dex.nodes.IContainer;
|
||||
import jadx.core.dex.nodes.IRegion;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.regions.Region;
|
||||
import jadx.core.dex.visitors.AbstractVisitor;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class CleanRegions extends AbstractVisitor {
|
||||
public class CleanRegions {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(CleanRegions.class);
|
||||
|
||||
@Override
|
||||
public void visit(MethodNode mth) throws JadxException {
|
||||
if (mth.isNoCode() || mth.getBasicBlocks().size() == 0)
|
||||
return;
|
||||
private CleanRegions() {
|
||||
}
|
||||
|
||||
public static void process(MethodNode mth) {
|
||||
if (mth.isNoCode() || mth.getBasicBlocks().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
IRegionVisitor removeEmptyBlocks = new AbstractRegionVisitor() {
|
||||
@Override
|
||||
public void enterRegion(MethodNode mth, IRegion region) {
|
||||
if (!(region instanceof Region))
|
||||
if (!(region instanceof Region)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (Iterator<IContainer> it = region.getSubBlocks().iterator(); it.hasNext(); ) {
|
||||
IContainer container = it.next();
|
||||
@@ -43,7 +44,6 @@ public class CleanRegions extends AbstractVisitor {
|
||||
}
|
||||
}
|
||||
};
|
||||
DepthRegionTraverser.traverseAll(mth, removeEmptyBlocks);
|
||||
|
||||
DepthRegionTraversal.traverseAll(mth, removeEmptyBlocks);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
package jadx.core.dex.visitors.regions;
|
||||
|
||||
import jadx.core.dex.nodes.IBlock;
|
||||
import jadx.core.dex.nodes.IContainer;
|
||||
import jadx.core.dex.nodes.IRegion;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.trycatch.ExceptionHandler;
|
||||
|
||||
public class DepthRegionTraversal {
|
||||
|
||||
private DepthRegionTraversal() {
|
||||
}
|
||||
|
||||
public static void traverse(MethodNode mth, IRegionVisitor visitor) {
|
||||
traverseInternal(mth, visitor, mth.getRegion());
|
||||
}
|
||||
|
||||
public static void traverseAll(MethodNode mth, IRegionVisitor visitor) {
|
||||
traverse(mth, visitor);
|
||||
for (ExceptionHandler h : mth.getExceptionHandlers()) {
|
||||
traverseInternal(mth, visitor, h.getHandlerRegion());
|
||||
}
|
||||
}
|
||||
|
||||
public static void traverseAllIterative(MethodNode mth, IRegionIterativeVisitor visitor) {
|
||||
boolean repeat;
|
||||
do {
|
||||
repeat = traverseAllIterativeIntern(mth, visitor);
|
||||
} while (repeat);
|
||||
}
|
||||
|
||||
private static void traverseInternal(MethodNode mth, IRegionVisitor visitor, IContainer container) {
|
||||
if (container instanceof IBlock) {
|
||||
visitor.processBlock(mth, (IBlock) container);
|
||||
} else if (container instanceof IRegion) {
|
||||
IRegion region = (IRegion) container;
|
||||
visitor.enterRegion(mth, region);
|
||||
for (IContainer subCont : region.getSubBlocks()) {
|
||||
traverseInternal(mth, visitor, subCont);
|
||||
}
|
||||
visitor.leaveRegion(mth, region);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean traverseAllIterativeIntern(MethodNode mth, IRegionIterativeVisitor visitor) {
|
||||
if (traverseIterativeInternal(mth, visitor, mth.getRegion())) {
|
||||
return true;
|
||||
}
|
||||
for (ExceptionHandler h : mth.getExceptionHandlers()) {
|
||||
if (traverseIterativeInternal(mth, visitor, h.getHandlerRegion())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean traverseIterativeInternal(MethodNode mth, IRegionIterativeVisitor visitor, IContainer container) {
|
||||
if (container instanceof IRegion) {
|
||||
IRegion region = (IRegion) container;
|
||||
if (visitor.visitRegion(mth, region)) {
|
||||
return true;
|
||||
}
|
||||
for (IContainer subCont : region.getSubBlocks()) {
|
||||
if (traverseIterativeInternal(mth, visitor, subCont)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
package jadx.core.dex.visitors.regions;
|
||||
|
||||
import jadx.core.dex.nodes.IBlock;
|
||||
import jadx.core.dex.nodes.IContainer;
|
||||
import jadx.core.dex.nodes.IRegion;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.trycatch.ExceptionHandler;
|
||||
|
||||
public class DepthRegionTraverser {
|
||||
|
||||
public static void traverse(MethodNode mth, IRegionVisitor visitor, IContainer container) {
|
||||
if (container instanceof IBlock) {
|
||||
visitor.processBlock(mth, (IBlock) container);
|
||||
} else if (container instanceof IRegion) {
|
||||
IRegion region = (IRegion) container;
|
||||
visitor.enterRegion(mth, region);
|
||||
for (IContainer subCont : region.getSubBlocks()) {
|
||||
traverse(mth, visitor, subCont);
|
||||
}
|
||||
visitor.leaveRegion(mth, region);
|
||||
}
|
||||
}
|
||||
|
||||
public static void traverseAll(MethodNode mth, IRegionVisitor visitor) {
|
||||
traverse(mth, visitor, mth.getRegion());
|
||||
|
||||
if (mth.getExceptionHandlers() != null) {
|
||||
for (ExceptionHandler h : mth.getExceptionHandlers())
|
||||
traverse(mth, visitor, h.getHandlerRegion());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package jadx.core.dex.visitors.regions;
|
||||
|
||||
import jadx.core.dex.nodes.IRegion;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
|
||||
public interface IRegionIterativeVisitor {
|
||||
|
||||
/**
|
||||
* If return 'true' traversal will be restarted.
|
||||
*/
|
||||
boolean visitRegion(MethodNode mth, IRegion region);
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user