Compare commits

...

64 Commits

Author SHA1 Message Date
Skylot fcb120a3ed core: suppress type error exception 2014-11-09 15:34:19 +03:00
Skylot 988628a2e7 core: fix variable declaration used in several loops 2014-11-09 14:55:33 +03:00
Skylot c24cdf5cc1 core: fix constructor instruction replacement 2014-11-08 20:38:22 +03:00
Skylot d748e004d2 core: fix missing parenthesis in conditions 2014-11-08 20:38:22 +03:00
Skylot 380b73d1b9 core: sort methods by source line number 2014-11-08 20:38:17 +03:00
Skylot ef85e29a9b core: improve 'break' and 'continue' insertion 2014-11-07 23:03:32 +03:00
Skylot 1daf5d1090 core: fix condition processing (resolve #25) 2014-11-07 21:39:27 +03:00
Skylot 9d2c0e4aea core: fix type inference and const inline for arrays 2014-11-07 20:07:18 +03:00
Skylot 7277ebb9c4 core: expand arrays for vararg arguments 2014-11-04 15:42:48 +03:00
Skylot c18074f6aa core: insert 'continue' instruction 2014-11-03 22:31:21 +03:00
Skylot 8a706193e7 core: fix indexed loop checks 2014-11-03 22:04:42 +03:00
Skylot 9d77f5f5df update dx library and dependencies 2014-11-03 15:22:49 +03:00
Skylot 6951d0e646 core: use NotNull and Nullable annotations 2014-11-03 15:13:52 +03:00
Skylot 73dd55eac2 core: fix code style issues 2014-11-03 15:11:48 +03:00
Skylot b5a9389cc6 core: fix variables inline in 'catch' block 2014-11-03 14:53:27 +03:00
Skylot d905c96fbe core: refactor 'catch' clause variable processing 2014-11-02 19:06:41 +03:00
Skylot 03f03f85af core: replace removed synthetic constructor 2014-11-01 15:46:57 +03:00
Skylot 2b00a8a406 core: disable parenthesis remove (break code in most cases) 2014-10-25 22:36:21 +04:00
Skylot f31c2dcd21 core: fix processing 'if' at loop end 2014-10-24 23:12:42 +04:00
Skylot 7699cfac02 tests: fix build on Windows 2014-10-23 21:30:46 +04:00
Skylot 5c48a457b4 fix code style issues 2014-10-19 19:07:15 +04:00
Skylot b5f439e1aa tests: reformat code 2014-10-19 18:01:32 +04:00
Skylot 202fe5a0a9 core: fix links for fields in nested classes 2014-10-18 23:08:15 +04:00
Skylot 68ccf57bd4 core: fix type detection for method arguments 2014-10-18 23:08:10 +04:00
Skylot 84970759d8 core: fix switch over enum with several enums in class 2014-10-14 22:38:29 +04:00
Skylot 53cac58ebe core: fix processing of last instruction in 'try' block 2014-10-11 22:44:26 +04:00
Skylot adc32ed319 fix minor issues 2014-10-11 22:44:26 +04:00
Skylot 7f0815a7b2 core tests: add option for compile test without debug info 2014-10-11 22:21:40 +04:00
Skylot 68f5565b63 core: improve processing of 'try/catch' and 'break' in loops 2014-10-11 22:21:30 +04:00
Skylot c552fb857d core tests: replace several classes in dynamic class loader, add additional checks 2014-10-07 22:19:54 +04:00
Skylot 8a4ec47b92 core: support break with label for simple cases 2014-09-29 23:44:36 +04:00
Skylot d281126337 core: fix processing try/catch in loop 2014-09-27 20:09:25 +04:00
Skylot 4fb6ada5ec core: fix type inference for phi nodes 2014-09-26 22:19:23 +04:00
Skylot ab924faa1e core: don't remove empty catch blocks 2014-09-22 22:48:25 +04:00
Skylot b12b129af7 travis: add jdk8 to build matrix 2014-09-22 22:34:44 +04:00
Skylot 017c6b4d42 core tests: compile decompiled code 2014-09-22 22:25:42 +04:00
Skylot d55cd5fbb4 core tests: organize directories 2014-09-22 22:02:42 +04:00
Skylot 13a6b1c8c6 core: add 'show inconsistent code' parameter 2014-09-20 13:43:55 +04:00
Skylot 0bc37e5d32 gui: fix jump manager 2014-09-20 13:01:20 +04:00
Skylot 46c8572887 core: restore switch over enum 2014-09-18 20:59:39 +04:00
Skylot e6b919007c gui: add new version notification 2014-09-16 22:03:18 +04:00
Skylot ac5a6096bb core: fix constructor call for moved arg (fix #20) 2014-09-13 18:55:17 +04:00
Skylot db527fbbda core: add api for write tests using smali 2014-09-13 18:55:17 +04:00
skylot 8f201f1fee Merge pull request #19 from NeoSpb/fix3
core: fix processing of debug info (markup of local variables)
2014-09-13 14:19:19 +04:00
Anton Dyachenko d1e0762c12 core: fix processing of debug info (markup of local variables) 2014-09-12 19:24:44 +04:00
Skylot 010ae99c69 core: restore simple for-each loop over iterable object 2014-09-07 16:49:02 +04:00
Skylot a4632d6e86 core: fix high memory usage while process conditions 2014-09-04 22:35:47 +04:00
Skylot 2a3162f869 core: don't set 'skip' flag for failed nested 'if' merge (issue #18) 2014-09-02 22:46:12 +04:00
skylot 2063fd0742 Merge pull request #17 from NeoSpb/fix2
Fix2 by NeoSpb
2014-09-02 20:47:14 +04:00
Anton Dyachenko 128fe8a839 core: fix resolving the instance field in the 2nd and more nested inner class 2014-09-02 20:05:15 +04:00
Anton Dyachenko 2478fc3a1b core: fix instance initializer producing (don't generate super() call) 2014-09-02 19:55:26 +04:00
Skylot 5a68d3bef7 core: restore for-each loop over array 2014-09-01 23:09:04 +04:00
Skylot 195eeceb62 core: restore simple indexed loops 2014-08-30 23:15:51 +04:00
Skylot ec8309af49 core: fix processing 'if' at loop end 2014-08-20 22:02:00 +04:00
skylot 627a4dc802 add contribution section to readme 2014-08-19 23:25:25 +04:00
Skylot e2018535ef core: add ternary conditions processing 2014-08-19 22:27:51 +04:00
Skylot ee56610f06 core: allow method name be same as class name (issue #15) 2014-08-18 20:45:50 +04:00
skylot fb9ff7748a Merge pull request #14 from NeoSpb/gui_preferences
gui: add saving preferences (open/save paths, flatten packages)
2014-08-17 13:22:08 +04:00
Anton Dyachenko cdfb46d9d3 gui: add saving preferences (open/save paths, flatten packages) 2014-08-17 12:01:46 +04:00
Skylot 5545a94a9e core: process nested ternary operators 2014-08-16 17:39:30 +04:00
Skylot 9e811d959b core: add method for print line numbers 2014-08-16 17:16:56 +04:00
Skylot 957d5394d2 refactor: add static methods for create DotGraphVisitor 2014-08-16 17:06:50 +04:00
Skylot 95afe1219e core: don't cache dex strings (old workaround for bug in dx) 2014-08-16 15:07:06 +04:00
Skylot 07937f1d71 bump version to 0.5.3 2014-08-16 15:06:55 +04:00
272 changed files with 6810 additions and 1636 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
language: java language: java
jdk: jdk:
- oraclejdk8
- oraclejdk7 - oraclejdk7
- openjdk7
- openjdk6 - openjdk6
before_install: before_install:
+14
View File
@@ -61,6 +61,20 @@ Example:
* edit 'jadx' script (jadx.bat on Windows) and setup bigger heap size: * edit 'jadx' script (jadx.bat on Windows) and setup bigger heap size:
`DEFAULT_JVM_OPTS="-Xmx2500M"` `DEFAULT_JVM_OPTS="-Xmx2500M"`
### Contribution
To support this project you can:
- Post thoughts about new features/optimizations that important to you
- Submit bug using one of following patterns:
* Java code examples which decompiles incorrectly
* Error log and link to _public available_ apk file or app page on Google play
And any other comments will be very helpfull,
because at current stage of development it is very time consuming
to **find** new bugs, design and implement new features.
Also I need to **prioritize** these task for complete most important at first.
--------------------------------------- ---------------------------------------
*Licensed under the Apache 2.0 License* *Licensed under the Apache 2.0 License*
+2 -1
View File
@@ -32,13 +32,14 @@ subprojects {
testCompile 'ch.qos.logback:logback-classic:1.1.2' testCompile 'ch.qos.logback:logback-classic:1.1.2'
testCompile 'junit:junit:4.11' testCompile 'junit:junit:4.11'
testCompile 'org.mockito:mockito-core:1.9.5' testCompile 'org.mockito:mockito-core:1.10.10'
testCompile 'org.spockframework:spock-core:0.7-groovy-2.0' testCompile 'org.spockframework:spock-core:0.7-groovy-2.0'
testCompile 'cglib:cglib-nodep:3.1' testCompile 'cglib:cglib-nodep:3.1'
} }
repositories { repositories {
mavenCentral() mavenCentral()
jcenter()
} }
jacocoTestReport { jacocoTestReport {
@@ -29,9 +29,12 @@ public final class JadxCLIArgs implements IJadxArgs {
@Parameter(names = {"-j", "--threads-count"}, description = "processing threads count") @Parameter(names = {"-j", "--threads-count"}, description = "processing threads count")
protected int threadsCount = Runtime.getRuntime().availableProcessors(); protected int threadsCount = Runtime.getRuntime().availableProcessors();
@Parameter(names = {"-f", "--fallback"}, description = "make simple dump (using goto instead of 'if', 'for', etc)", help = true) @Parameter(names = {"-f", "--fallback"}, description = "make simple dump (using goto instead of 'if', 'for', etc)")
protected boolean fallbackMode = false; protected boolean fallbackMode = false;
@Parameter(names = {"--show-bad-code"}, description = "show inconsistent code (incorrectly decompiled)")
protected boolean showInconsistentCode = false;
@Parameter(names = {"--cfg"}, description = "save methods control flow graph to dot file") @Parameter(names = {"--cfg"}, description = "save methods control flow graph to dot file")
protected boolean cfgOutput = false; protected boolean cfgOutput = false;
@@ -181,6 +184,11 @@ public final class JadxCLIArgs implements IJadxArgs {
return fallbackMode; return fallbackMode;
} }
@Override
public boolean isShowInconsistentCode() {
return showInconsistentCode;
}
@Override @Override
public boolean isVerbose() { public boolean isVerbose() {
return verbose; return verbose;
+6 -2
View File
@@ -1,9 +1,13 @@
ext.jadxClasspath = 'clsp-data/android-4.3.jar' ext.jadxClasspath = 'clsp-data/android-4.3.jar'
dependencies { dependencies {
compile files('lib/dx-1.8.jar')
compile 'org.ow2.asm:asm:5.0.3'
runtime files(jadxClasspath) runtime files(jadxClasspath)
compile files('lib/dx-1.10.jar')
compile 'org.ow2.asm:asm:5.0.3'
compile 'com.intellij:annotations:12.0'
testCompile 'org.smali:smali:2.0.3'
} }
task packTests(type: Jar) { task packTests(type: Jar) {
Binary file not shown.
Binary file not shown.
@@ -29,6 +29,11 @@ public class DefaultJadxArgs implements IJadxArgs {
return false; return false;
} }
@Override
public boolean isShowInconsistentCode() {
return false;
}
@Override @Override
public boolean isVerbose() { public boolean isVerbose() {
return false; return false;
@@ -13,5 +13,7 @@ public interface IJadxArgs {
boolean isFallbackMode(); boolean isFallbackMode();
boolean isShowInconsistentCode();
boolean isVerbose(); boolean isVerbose();
} }
+24 -21
View File
@@ -116,28 +116,31 @@ public final class JavaClass implements JavaNode {
public CodePosition getDefinitionPosition(int line, int offset) { public CodePosition getDefinitionPosition(int line, int offset) {
Map<CodePosition, Object> map = getCodeAnnotations(); Map<CodePosition, Object> map = getCodeAnnotations();
Object obj = map.get(new CodePosition(line, offset)); Object obj = map.get(new CodePosition(line, offset));
if (obj instanceof LineAttrNode) { if (!(obj instanceof LineAttrNode)) {
ClassNode clsNode = null; return 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 null; 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.getTopParentClass();
JavaClass jCls = decompiler.findJavaClass(clsNode);
if (jCls == null) {
return null;
}
jCls.decompile();
int defLine = ((LineAttrNode) obj).getDecompiledLine();
if (defLine == 0) {
return null;
}
return new CodePosition(jCls, defLine, 0);
} }
public Integer getSourceLine(int decompiledLine) { public Integer getSourceLine(int decompiledLine) {
+6 -4
View File
@@ -18,6 +18,7 @@ import jadx.core.dex.visitors.ReSugarCode;
import jadx.core.dex.visitors.SimplifyVisitor; import jadx.core.dex.visitors.SimplifyVisitor;
import jadx.core.dex.visitors.regions.CheckRegions; import jadx.core.dex.visitors.regions.CheckRegions;
import jadx.core.dex.visitors.regions.IfRegionVisitor; import jadx.core.dex.visitors.regions.IfRegionVisitor;
import jadx.core.dex.visitors.regions.LoopRegionVisitor;
import jadx.core.dex.visitors.regions.ProcessVariables; import jadx.core.dex.visitors.regions.ProcessVariables;
import jadx.core.dex.visitors.regions.RegionMakerVisitor; import jadx.core.dex.visitors.regions.RegionMakerVisitor;
import jadx.core.dex.visitors.regions.ReturnVisitor; import jadx.core.dex.visitors.regions.ReturnVisitor;
@@ -59,7 +60,7 @@ public class Jadx {
passes.add(new DebugInfoVisitor()); passes.add(new DebugInfoVisitor());
passes.add(new TypeInference()); passes.add(new TypeInference());
if (args.isRawCFGOutput()) { if (args.isRawCFGOutput()) {
passes.add(new DotGraphVisitor(outDir, false, true)); passes.add(DotGraphVisitor.dumpRaw(outDir));
} }
passes.add(new ConstInlinerVisitor()); passes.add(new ConstInlinerVisitor());
@@ -72,7 +73,7 @@ public class Jadx {
passes.add(new CodeShrinker()); passes.add(new CodeShrinker());
passes.add(new ReSugarCode()); passes.add(new ReSugarCode());
if (args.isCFGOutput()) { if (args.isCFGOutput()) {
passes.add(new DotGraphVisitor(outDir, false)); passes.add(DotGraphVisitor.dump(outDir));
} }
passes.add(new RegionMakerVisitor()); passes.add(new RegionMakerVisitor());
@@ -81,16 +82,17 @@ public class Jadx {
passes.add(new CodeShrinker()); passes.add(new CodeShrinker());
passes.add(new SimplifyVisitor()); passes.add(new SimplifyVisitor());
passes.add(new ProcessVariables());
passes.add(new CheckRegions()); passes.add(new CheckRegions());
if (args.isCFGOutput()) { if (args.isCFGOutput()) {
passes.add(new DotGraphVisitor(outDir, true)); passes.add(DotGraphVisitor.dumpRegions(outDir));
} }
passes.add(new MethodInlineVisitor()); passes.add(new MethodInlineVisitor());
passes.add(new ClassModifier()); passes.add(new ClassModifier());
passes.add(new PrepareForCodeGen()); passes.add(new PrepareForCodeGen());
passes.add(new LoopRegionVisitor());
passes.add(new ProcessVariables());
} }
passes.add(new CodeGen(args)); passes.add(new CodeGen(args));
return passes; return passes;
@@ -22,7 +22,7 @@ public final class ProcessClass {
DepthTraversal.visit(visitor, cls); DepthTraversal.visit(visitor, cls);
} }
} catch (Exception e) { } catch (Exception e) {
LOG.error("Class process exception: " + cls, e); LOG.error("Class process exception: {}", cls, e);
} finally { } finally {
cls.unload(); cls.unload();
} }
@@ -3,9 +3,9 @@ package jadx.core.clsp;
import jadx.core.dex.info.ClassInfo; import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.RootNode; import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.DecodeException; import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.exceptions.JadxRuntimeException; import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.utils.files.FileUtils;
import java.io.BufferedOutputStream; import java.io.BufferedOutputStream;
import java.io.DataInputStream; import java.io.DataInputStream;
@@ -96,13 +96,13 @@ public class ClsSet {
private static NClass getCls(String fullName, Map<String, NClass> names) { private static NClass getCls(String fullName, Map<String, NClass> names) {
NClass id = names.get(fullName); NClass id = names.get(fullName);
if (id == null && !names.containsKey(fullName)) { if (id == null && !names.containsKey(fullName)) {
LOG.warn("Class not found: " + fullName); LOG.warn("Class not found: {}", fullName);
} }
return id; return id;
} }
void save(File output) throws IOException { void save(File output) throws IOException {
Utils.makeDirsForFile(output); FileUtils.makeDirsForFile(output);
BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(output)); BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(output));
try { try {
@@ -132,7 +132,7 @@ public class ClsSet {
out.writeBytes(JADX_CLS_SET_HEADER); out.writeBytes(JADX_CLS_SET_HEADER);
out.writeByte(VERSION); out.writeByte(VERSION);
LOG.info("Classes count: " + classes.length); LOG.info("Classes count: {}", classes.length);
out.writeInt(classes.length); out.writeInt(classes.length);
for (NClass cls : classes) { for (NClass cls : classes) {
writeString(out, cls.getName()); writeString(out, cls.getName());
@@ -39,7 +39,7 @@ public class ConvertToClsSet {
} }
} }
for (InputFile inputFile : inputFiles) { for (InputFile inputFile : inputFiles) {
LOG.info("Loaded: " + inputFile.getFile()); LOG.info("Loaded: {}", inputFile.getFile());
} }
RootNode root = new RootNode(); RootNode root = new RootNode();
@@ -48,7 +48,7 @@ public class ConvertToClsSet {
ClsSet set = new ClsSet(); ClsSet set = new ClsSet();
set.load(root); set.load(root);
set.save(output); set.save(output);
LOG.info("Output: " + output); LOG.info("Output: {}", output);
LOG.info("done"); LOG.info("done");
} }
@@ -43,7 +43,7 @@ public class AnnotationGen {
public void addForParameter(CodeWriter code, MethodParameters paramsAnnotations, int n) { public void addForParameter(CodeWriter code, MethodParameters paramsAnnotations, int n) {
AnnotationsList aList = paramsAnnotations.getParamList().get(n); AnnotationsList aList = paramsAnnotations.getParamList().get(n);
if (aList == null || aList.size() == 0) { if (aList == null || aList.isEmpty()) {
return; return;
} }
for (Annotation a : aList.getAll()) { for (Annotation a : aList.getAll()) {
@@ -54,7 +54,7 @@ public class AnnotationGen {
private void add(IAttributeNode node, CodeWriter code) { private void add(IAttributeNode node, CodeWriter code) {
AnnotationsList aList = node.get(AType.ANNOTATION_LIST); AnnotationsList aList = node.get(AType.ANNOTATION_LIST);
if (aList == null || aList.size() == 0) { if (aList == null || aList.isEmpty()) {
return; return;
} }
for (Annotation a : aList.getAll()) { for (Annotation a : aList.getAll()) {
@@ -150,9 +150,9 @@ public class AnnotationGen {
// must be a static field // must be a static field
FieldInfo field = (FieldInfo) val; FieldInfo field = (FieldInfo) val;
InsnGen.makeStaticFieldAccess(code, field, classGen); InsnGen.makeStaticFieldAccess(code, field, classGen);
} else if (val instanceof List) { } else if (val instanceof Iterable) {
code.add('{'); code.add('{');
Iterator<?> it = ((List) val).iterator(); Iterator<?> it = ((Iterable) val).iterator();
while (it.hasNext()) { while (it.hasNext()) {
Object obj = it.next(); Object obj = it.next();
encodeValue(code, obj); encodeValue(code, obj);
@@ -1,5 +1,6 @@
package jadx.core.codegen; package jadx.core.codegen;
import jadx.api.IJadxArgs;
import jadx.core.Consts; import jadx.core.Consts;
import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.AType;
@@ -23,6 +24,7 @@ import jadx.core.utils.exceptions.CodegenException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
@@ -38,14 +40,28 @@ import com.android.dx.rop.code.AccessFlags;
public class ClassGen { public class ClassGen {
private static final Logger LOG = LoggerFactory.getLogger(ClassGen.class); private static final Logger LOG = LoggerFactory.getLogger(ClassGen.class);
public static final Comparator<MethodNode> METHOD_LINE_COMPARATOR = new Comparator<MethodNode>() {
@Override
public int compare(MethodNode a, MethodNode b) {
return Utils.compare(a.getSourceLine(), b.getSourceLine());
}
};
private final ClassNode cls; private final ClassNode cls;
private final ClassGen parentGen; private final ClassGen parentGen;
private final AnnotationGen annotationGen; private final AnnotationGen annotationGen;
private final boolean fallback; private final boolean fallback;
private boolean showInconsistentCode = false;
private final Set<ClassInfo> imports = new HashSet<ClassInfo>(); private final Set<ClassInfo> imports = new HashSet<ClassInfo>();
private int clsDeclLine; private int clsDeclLine;
public ClassGen(ClassNode cls, ClassGen parentClsGen, IJadxArgs jadxArgs) {
this(cls, parentClsGen, jadxArgs.isFallbackMode());
this.showInconsistentCode = jadxArgs.isShowInconsistentCode();
}
public ClassGen(ClassNode cls, ClassGen parentClsGen, boolean fallback) { public ClassGen(ClassNode cls, ClassGen parentClsGen, boolean fallback) {
this.cls = cls; this.cls = cls;
this.parentGen = parentClsGen; this.parentGen = parentClsGen;
@@ -108,6 +124,11 @@ public class ClassGen {
.remove(AccessFlags.ACC_STATIC); .remove(AccessFlags.ACC_STATIC);
} }
// 'static' modifier not allowed for top classes (not inner)
if (!cls.getClassInfo().isInner()) {
af = af.remove(AccessFlags.ACC_STATIC);
}
annotationGen.addForClass(clsCode); annotationGen.addForClass(clsCode);
insertSourceFileInfo(clsCode, cls); insertSourceFileInfo(clsCode, cls);
clsCode.startLine(af.makeString()); clsCode.startLine(af.makeString());
@@ -135,7 +156,7 @@ public class ClassGen {
clsCode.add(' '); clsCode.add(' ');
} }
if (cls.getInterfaces().size() > 0 && !af.isAnnotation()) { if (!cls.getInterfaces().isEmpty() && !af.isAnnotation()) {
if (cls.getAccessFlags().isInterface()) { if (cls.getAccessFlags().isInterface()) {
clsCode.add("extends "); clsCode.add("extends ");
} else { } else {
@@ -226,7 +247,8 @@ public class ClassGen {
} }
private void addMethods(CodeWriter code) { private void addMethods(CodeWriter code) {
for (MethodNode mth : cls.getMethods()) { List<MethodNode> methods = sortMethodsByLine(cls.getMethods());
for (MethodNode mth : methods) {
if (mth.contains(AFlag.DONT_GENERATE)) { if (mth.contains(AFlag.DONT_GENERATE)) {
continue; continue;
} }
@@ -242,6 +264,12 @@ public class ClassGen {
} }
} }
private static List<MethodNode> sortMethodsByLine(List<MethodNode> methods) {
List<MethodNode> out = new ArrayList<MethodNode>(methods);
Collections.sort(out, METHOD_LINE_COMPARATOR);
return out;
}
private boolean isMethodsPresents() { private boolean isMethodsPresents() {
for (MethodNode mth : cls.getMethods()) { for (MethodNode mth : cls.getMethods()) {
if (!mth.contains(AFlag.DONT_GENERATE)) { if (!mth.contains(AFlag.DONT_GENERATE)) {
@@ -269,6 +297,9 @@ public class ClassGen {
code.startLine("/* JADX WARNING: inconsistent code. */"); code.startLine("/* JADX WARNING: inconsistent code. */");
code.startLine("/* Code decompiled incorrectly, please refer to instructions dump. */"); code.startLine("/* Code decompiled incorrectly, please refer to instructions dump. */");
ErrorsCounter.methodError(mth, "Inconsistent code"); ErrorsCounter.methodError(mth, "Inconsistent code");
if (showInconsistentCode) {
mth.remove(AFlag.INCONSISTENT_CODE);
}
} }
MethodGen mthGen; MethodGen mthGen;
if (badCode || mth.contains(AType.JADX_ERROR)) { if (badCode || mth.contains(AType.JADX_ERROR)) {
@@ -331,7 +362,7 @@ public class ClassGen {
for (Iterator<EnumField> it = enumFields.getFields().iterator(); it.hasNext(); ) { for (Iterator<EnumField> it = enumFields.getFields().iterator(); it.hasNext(); ) {
EnumField f = it.next(); EnumField f = it.next();
code.startLine(f.getName()); code.startLine(f.getName());
if (f.getArgs().size() != 0) { if (!f.getArgs().isEmpty()) {
code.add('('); code.add('(');
for (Iterator<InsnArg> aIt = f.getArgs().iterator(); aIt.hasNext(); ) { for (Iterator<InsnArg> aIt = f.getArgs().iterator(); aIt.hasNext(); ) {
InsnArg arg = aIt.next(); InsnArg arg = aIt.next();
@@ -387,8 +418,8 @@ public class ClassGen {
code.attachAnnotation(classNode); code.attachAnnotation(classNode);
} }
String baseClass = useClassInternal(cls.getClassInfo(), classInfo); String baseClass = useClassInternal(cls.getClassInfo(), classInfo);
ArgType[] generics = classInfo.getType().getGenericTypes();
code.add(baseClass); code.add(baseClass);
ArgType[] generics = classInfo.getType().getGenericTypes();
if (generics != null) { if (generics != null) {
code.add('<'); code.add('<');
int len = generics.length; int len = generics.length;
@@ -435,7 +466,7 @@ public class ClassGen {
if (classNode != null && !classNode.getAccessFlags().isPublic()) { if (classNode != null && !classNode.getAccessFlags().isPublic()) {
return shortName; return shortName;
} }
if (searchCollision(cls.dex(), useCls, shortName)) { if (searchCollision(cls.dex(), useCls, classInfo)) {
return fullName; return fullName;
} }
if (classInfo.getPackage().equals(useCls.getPackage())) { if (classInfo.getPackage().equals(useCls.getPackage())) {
@@ -481,22 +512,24 @@ public class ClassGen {
return false; return false;
} }
private static boolean searchCollision(DexNode dex, ClassInfo useCls, String shortName) { private static boolean searchCollision(DexNode dex, ClassInfo useCls, ClassInfo searchCls) {
if (useCls == null) { if (useCls == null) {
return false; return false;
} }
String shortName = searchCls.getShortName();
if (useCls.getShortName().equals(shortName)) { if (useCls.getShortName().equals(shortName)) {
return true; return true;
} }
ClassNode classNode = dex.resolveClass(useCls); ClassNode classNode = dex.resolveClass(useCls);
if (classNode != null) { if (classNode != null) {
for (ClassNode inner : classNode.getInnerClasses()) { for (ClassNode inner : classNode.getInnerClasses()) {
if (inner.getShortName().equals(shortName)) { if (inner.getShortName().equals(shortName)
&& !inner.getClassInfo().equals(searchCls)) {
return true; return true;
} }
} }
} }
return searchCollision(dex, useCls.getParentClass(), shortName); return searchCollision(dex, useCls.getParentClass(), searchCls);
} }
private void insertSourceFileInfo(CodeWriter code, AttrNode node) { private void insertSourceFileInfo(CodeWriter code, AttrNode node) {
@@ -15,15 +15,11 @@ public class CodeGen extends AbstractVisitor {
@Override @Override
public boolean visit(ClassNode cls) throws CodegenException { public boolean visit(ClassNode cls) throws CodegenException {
ClassGen clsGen = new ClassGen(cls, null, isFallbackMode()); ClassGen clsGen = new ClassGen(cls, null, args);
CodeWriter clsCode = clsGen.makeClass(); CodeWriter clsCode = clsGen.makeClass();
clsCode.finish(); clsCode.finish();
cls.setCode(clsCode); cls.setCode(clsCode);
return false; return false;
} }
public boolean isFallbackMode() {
return args.isFallbackMode();
}
} }
@@ -2,7 +2,7 @@ package jadx.core.codegen;
import jadx.api.CodePosition; import jadx.api.CodePosition;
import jadx.core.dex.attributes.nodes.LineAttrNode; import jadx.core.dex.attributes.nodes.LineAttrNode;
import jadx.core.utils.Utils; import jadx.core.utils.files.FileUtils;
import java.io.File; import java.io.File;
import java.io.PrintWriter; import java.io.PrintWriter;
@@ -22,6 +22,8 @@ public class CodeWriter {
public static final String NL = System.getProperty("line.separator"); public static final String NL = System.getProperty("line.separator");
public static final String INDENT = " "; public static final String INDENT = " ";
private static final boolean ADD_LINE_NUMBERS = false;
private static final String[] INDENT_CACHE = { private static final String[] INDENT_CACHE = {
"", "",
INDENT, INDENT,
@@ -43,6 +45,9 @@ public class CodeWriter {
public CodeWriter() { public CodeWriter() {
this.indent = 0; this.indent = 0;
this.indentStr = ""; this.indentStr = "";
if (ADD_LINE_NUMBERS) {
incIndent(2);
}
} }
public CodeWriter startLine() { public CodeWriter startLine() {
@@ -65,6 +70,26 @@ public class CodeWriter {
return this; return this;
} }
public CodeWriter startLineWithNum(int sourceLine) {
if (sourceLine == 0) {
startLine();
return this;
}
if (ADD_LINE_NUMBERS) {
newLine();
attachSourceLine(sourceLine);
String ln = "/* " + sourceLine + " */ ";
add(ln);
if (indentStr.length() > ln.length()) {
add(indentStr.substring(ln.length()));
}
} else {
startLine();
attachSourceLine(sourceLine);
}
return this;
}
public CodeWriter add(String str) { public CodeWriter add(String str) {
buf.append(str); buf.append(str);
offset += str.length(); offset += str.length();
@@ -263,11 +288,11 @@ public class CodeWriter {
PrintWriter out = null; PrintWriter out = null;
try { try {
Utils.makeDirsForFile(file); FileUtils.makeDirsForFile(file);
out = new PrintWriter(file, "UTF-8"); out = new PrintWriter(file, "UTF-8");
String code = buf.toString(); String code = buf.toString();
code = removeFirstEmptyLine(code); code = removeFirstEmptyLine(code);
out.print(code); out.println(code);
} catch (Exception e) { } catch (Exception e) {
LOG.error("Save file error", e); LOG.error("Save file error", e);
} finally { } finally {
@@ -8,14 +8,16 @@ import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.InsnWrapArg; import jadx.core.dex.instructions.args.InsnWrapArg;
import jadx.core.dex.instructions.args.LiteralArg; import jadx.core.dex.instructions.args.LiteralArg;
import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.regions.Compare; import jadx.core.dex.regions.conditions.Compare;
import jadx.core.dex.regions.IfCondition; import jadx.core.dex.regions.conditions.IfCondition;
import jadx.core.dex.regions.IfCondition.Mode; import jadx.core.dex.regions.conditions.IfCondition.Mode;
import jadx.core.utils.ErrorsCounter; import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.exceptions.CodegenException; import jadx.core.utils.exceptions.CodegenException;
import jadx.core.utils.exceptions.JadxRuntimeException; import jadx.core.utils.exceptions.JadxRuntimeException;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedList;
import java.util.Queue;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -23,42 +25,83 @@ import org.slf4j.LoggerFactory;
public class ConditionGen extends InsnGen { public class ConditionGen extends InsnGen {
private static final Logger LOG = LoggerFactory.getLogger(ConditionGen.class); private static final Logger LOG = LoggerFactory.getLogger(ConditionGen.class);
private static class CondStack {
private final Queue<IfCondition> stack = new LinkedList<IfCondition>();
public Queue<IfCondition> getStack() {
return stack;
}
public void push(IfCondition cond) {
stack.add(cond);
}
public IfCondition pop() {
return stack.poll();
}
}
public ConditionGen(InsnGen insnGen) { public ConditionGen(InsnGen insnGen) {
super(insnGen.mgen, insnGen.fallback); super(insnGen.mgen, insnGen.fallback);
} }
void add(CodeWriter code, IfCondition condition) throws CodegenException { void add(CodeWriter code, IfCondition condition) throws CodegenException {
add(code, new CondStack(), condition);
}
void wrap(CodeWriter code, IfCondition condition) throws CodegenException {
wrap(code, new CondStack(), condition);
}
private void add(CodeWriter code, CondStack stack, IfCondition condition) throws CodegenException {
stack.push(condition);
switch (condition.getMode()) { switch (condition.getMode()) {
case COMPARE: case COMPARE:
addCompare(code, condition.getCompare()); addCompare(code, stack, condition.getCompare());
break;
case TERNARY:
addTernary(code, stack, condition);
break; break;
case NOT: case NOT:
addNot(code, condition); addNot(code, stack, condition);
break; break;
case AND: case AND:
case OR: case OR:
addAndOr(code, condition); addAndOr(code, stack, condition);
break; break;
default: default:
throw new JadxRuntimeException("Unknown condition mode: " + condition); throw new JadxRuntimeException("Unknown condition mode: " + condition.getMode());
} }
stack.pop();
} }
void wrap(CodeWriter code, IfCondition cond) throws CodegenException { private void wrap(CodeWriter code, CondStack stack, IfCondition cond) throws CodegenException {
boolean wrap = isWrapNeeded(cond); boolean wrap = isWrapNeeded(cond);
if (wrap) { if (wrap) {
code.add('('); code.add('(');
} }
add(code, cond); add(code, stack, cond);
if (wrap) { if (wrap) {
code.add(')'); code.add(')');
} }
} }
private void addCompare(CodeWriter code, Compare compare) throws CodegenException { private void wrap(CodeWriter code, InsnArg firstArg) throws CodegenException {
boolean wrap = isArgWrapNeeded(firstArg);
if (wrap) {
code.add('(');
}
addArg(code, firstArg, false);
if (wrap) {
code.add(')');
}
}
private void addCompare(CodeWriter code, CondStack stack, Compare compare) throws CodegenException {
IfOp op = compare.getOp(); IfOp op = compare.getOp();
InsnArg firstArg = compare.getA(); InsnArg firstArg = compare.getA();
InsnArg secondArg = compare.getB(); InsnArg secondArg = compare.getB();
@@ -71,19 +114,16 @@ public class ConditionGen extends InsnGen {
} }
if (op == IfOp.EQ) { if (op == IfOp.EQ) {
// == true // == true
addArg(code, firstArg, false); if (stack.getStack().size() == 1) {
addArg(code, firstArg, false);
} else {
wrap(code, firstArg);
}
return; return;
} else if (op == IfOp.NE) { } else if (op == IfOp.NE) {
// != true // != true
code.add('!'); code.add('!');
boolean wrap = isArgWrapNeeded(firstArg); wrap(code, firstArg);
if (wrap) {
code.add('(');
}
addArg(code, firstArg, false);
if (wrap) {
code.add(')');
}
return; return;
} }
LOG.warn(ErrorsCounter.formatErrorMsg(mth, "Unsupported boolean condition " + op.getSymbol())); LOG.warn(ErrorsCounter.formatErrorMsg(mth, "Unsupported boolean condition " + op.getSymbol()));
@@ -94,16 +134,24 @@ public class ConditionGen extends InsnGen {
addArg(code, secondArg, isArgWrapNeeded(secondArg)); addArg(code, secondArg, isArgWrapNeeded(secondArg));
} }
private void addNot(CodeWriter code, IfCondition condition) throws CodegenException { private void addTernary(CodeWriter code, CondStack stack, IfCondition condition) throws CodegenException {
code.add('!'); add(code, stack, condition.first());
wrap(code, condition.getArgs().get(0)); code.add(" ? ");
add(code, stack, condition.second());
code.add(" : ");
add(code, stack, condition.third());
} }
private void addAndOr(CodeWriter code, IfCondition condition) throws CodegenException { private void addNot(CodeWriter code, CondStack stack, IfCondition condition) throws CodegenException {
code.add('!');
wrap(code, stack, condition.getArgs().get(0));
}
private void addAndOr(CodeWriter code, CondStack stack, IfCondition condition) throws CodegenException {
String mode = condition.getMode() == Mode.AND ? " && " : " || "; String mode = condition.getMode() == Mode.AND ? " && " : " || ";
Iterator<IfCondition> it = condition.getArgs().iterator(); Iterator<IfCondition> it = condition.getArgs().iterator();
while (it.hasNext()) { while (it.hasNext()) {
wrap(code, it.next()); wrap(code, stack, it.next());
if (it.hasNext()) { if (it.hasNext()) {
code.add(mode); code.add(mode);
} }
@@ -111,7 +159,13 @@ public class ConditionGen extends InsnGen {
} }
private boolean isWrapNeeded(IfCondition condition) { private boolean isWrapNeeded(IfCondition condition) {
return !condition.isCompare() && condition.getMode() != Mode.NOT; if (condition.isCompare()) {
return false;
}
if (condition.getMode() != Mode.NOT) {
return true;
}
return false;
} }
private static boolean isArgWrapNeeded(InsnArg arg) { private static boolean isArgWrapNeeded(InsnArg arg) {
@@ -3,6 +3,7 @@ package jadx.core.codegen;
import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.FieldReplaceAttr; import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
import jadx.core.dex.attributes.nodes.MethodInlineAttr; import jadx.core.dex.attributes.nodes.MethodInlineAttr;
import jadx.core.dex.info.ClassInfo; import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.FieldInfo; import jadx.core.dex.info.FieldInfo;
@@ -24,7 +25,7 @@ import jadx.core.dex.instructions.args.FieldArg;
import jadx.core.dex.instructions.args.InsnArg; import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.InsnWrapArg; import jadx.core.dex.instructions.args.InsnWrapArg;
import jadx.core.dex.instructions.args.LiteralArg; import jadx.core.dex.instructions.args.LiteralArg;
import jadx.core.dex.instructions.args.NamedArg; import jadx.core.dex.instructions.args.Named;
import jadx.core.dex.instructions.args.RegisterArg; import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.mods.ConstructorInsn; import jadx.core.dex.instructions.mods.ConstructorInsn;
import jadx.core.dex.instructions.mods.TernaryInsn; import jadx.core.dex.instructions.mods.TernaryInsn;
@@ -46,7 +47,9 @@ import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -58,9 +61,10 @@ public class InsnGen {
protected final RootNode root; protected final RootNode root;
protected final boolean fallback; protected final boolean fallback;
private enum Flags { protected enum Flags {
BODY_ONLY, BODY_ONLY,
BODY_ONLY_NOWRAP, BODY_ONLY_NOWRAP,
INLINE
} }
public InsnGen(MethodGen mgen, boolean fallback) { public InsnGen(MethodGen mgen, boolean fallback) {
@@ -95,7 +99,7 @@ public class InsnGen {
Flags flag = wrap ? Flags.BODY_ONLY : Flags.BODY_ONLY_NOWRAP; Flags flag = wrap ? Flags.BODY_ONLY : Flags.BODY_ONLY_NOWRAP;
makeInsn(((InsnWrapArg) arg).getWrapInsn(), code, flag); makeInsn(((InsnWrapArg) arg).getWrapInsn(), code, flag);
} else if (arg.isNamed()) { } else if (arg.isNamed()) {
code.add(((NamedArg) arg).getName()); code.add(((Named) arg).getName());
} else if (arg.isField()) { } else if (arg.isField()) {
FieldArg f = (FieldArg) arg; FieldArg f = (FieldArg) arg;
if (f.isStatic()) { if (f.isStatic()) {
@@ -128,7 +132,14 @@ public class InsnGen {
} }
private void instanceField(CodeWriter code, FieldInfo field, InsnArg arg) throws CodegenException { private void instanceField(CodeWriter code, FieldInfo field, InsnArg arg) throws CodegenException {
FieldNode fieldNode = mth.getParentClass().searchField(field); ClassNode pCls = mth.getParentClass();
FieldNode fieldNode = pCls.searchField(field);
while (fieldNode == null
&& pCls.getParentClass() != pCls
&& pCls.getParentClass() != null) {
pCls = pCls.getParentClass();
fieldNode = pCls.searchField(field);
}
if (fieldNode != null) { if (fieldNode != null) {
FieldReplaceAttr replace = fieldNode.get(AType.FIELD_REPLACE); FieldReplaceAttr replace = fieldNode.get(AType.FIELD_REPLACE);
if (replace != null) { if (replace != null) {
@@ -178,7 +189,7 @@ public class InsnGen {
mgen.getClassGen().useClass(code, cls); mgen.getClassGen().useClass(code, cls);
} }
private void useType(CodeWriter code, ArgType type) { protected void useType(CodeWriter code, ArgType type) {
mgen.getClassGen().useType(code, type); mgen.getClassGen().useType(code, type);
} }
@@ -186,26 +197,27 @@ public class InsnGen {
return makeInsn(insn, code, null); return makeInsn(insn, code, null);
} }
private boolean makeInsn(InsnNode insn, CodeWriter code, Flags flag) throws CodegenException { protected boolean makeInsn(InsnNode insn, CodeWriter code, Flags flag) throws CodegenException {
try { try {
if (insn.getType() == InsnType.NOP) { if (insn.getType() == InsnType.NOP) {
return false; return false;
} }
EnumSet<Flags> state = EnumSet.noneOf(Flags.class); Set<Flags> state = EnumSet.noneOf(Flags.class);
if (flag == Flags.BODY_ONLY || flag == Flags.BODY_ONLY_NOWRAP) { if (flag == Flags.BODY_ONLY || flag == Flags.BODY_ONLY_NOWRAP) {
state.add(flag); state.add(flag);
makeInsnBody(code, insn, state); makeInsnBody(code, insn, state);
} else { } else {
code.startLine(); if (flag != Flags.INLINE) {
if (insn.getSourceLine() != 0) { code.startLineWithNum(insn.getSourceLine());
code.attachSourceLine(insn.getSourceLine());
} }
if (insn.getResult() != null && insn.getType() != InsnType.ARITH_ONEARG) { if (insn.getResult() != null && !insn.contains(AFlag.ARITH_ONEARG)) {
assignVar(code, insn); assignVar(code, insn);
code.add(" = "); code.add(" = ");
} }
makeInsnBody(code, insn, state); makeInsnBody(code, insn, state);
code.add(';'); if (flag != Flags.INLINE) {
code.add(';');
}
} }
} catch (Throwable th) { } catch (Throwable th) {
throw new CodegenException(mth, "Error generate insn: " + insn, th); throw new CodegenException(mth, "Error generate insn: " + insn, th);
@@ -213,7 +225,7 @@ public class InsnGen {
return true; return true;
} }
private void makeInsnBody(CodeWriter code, InsnNode insn, EnumSet<Flags> state) throws CodegenException { private void makeInsnBody(CodeWriter code, InsnNode insn, Set<Flags> state) throws CodegenException {
switch (insn.getType()) { switch (insn.getType()) {
case CONST_STR: case CONST_STR:
String str = ((ConstStringNode) insn).getString(); String str = ((ConstStringNode) insn).getString();
@@ -255,10 +267,6 @@ public class InsnGen {
makeArith((ArithNode) insn, code, state); makeArith((ArithNode) insn, code, state);
break; break;
case ARITH_ONEARG:
makeArithOneArg((ArithNode) insn, code);
break;
case NEG: { case NEG: {
boolean wrap = state.contains(Flags.BODY_ONLY); boolean wrap = state.contains(Flags.BODY_ONLY);
if (wrap) { if (wrap) {
@@ -283,6 +291,10 @@ public class InsnGen {
case BREAK: case BREAK:
code.add("break"); code.add("break");
LoopLabelAttr labelAttr = insn.get(AType.LOOP_LABEL);
if (labelAttr != null) {
code.add(' ').add(mgen.getNameGen().getLoopLabel(labelAttr));
}
break; break;
case CONTINUE: case CONTINUE:
@@ -304,7 +316,7 @@ public class InsnGen {
addArg(code, insn.getArg(0)); addArg(code, insn.getArg(0));
code.add(" == "); code.add(" == ");
addArg(code, insn.getArg(1)); addArg(code, insn.getArg(1));
code.add("? 0 : -1))"); code.add(" ? 0 : -1))");
break; break;
case INSTANCE_OF: { case INSTANCE_OF: {
@@ -425,19 +437,11 @@ public class InsnGen {
} }
break; break;
case MOVE_EXCEPTION:
if (isFallback()) {
code.add("move-exception");
} else {
addArg(code, insn.getArg(0));
}
break;
case TERNARY: case TERNARY:
makeTernary((TernaryInsn) insn, code, state); makeTernary((TernaryInsn) insn, code, state);
break; break;
case ARGS: case ONE_ARG:
addArg(code, insn.getArg(0)); addArg(code, insn.getArg(0));
break; break;
@@ -461,6 +465,11 @@ public class InsnGen {
code.add("goto ").add(MethodGen.getLabelName(((GotoNode) insn).getTarget())); code.add("goto ").add(MethodGen.getLabelName(((GotoNode) insn).getTarget()));
break; break;
case MOVE_EXCEPTION:
assert isFallback();
code.add("move-exception");
break;
case SWITCH: case SWITCH:
assert isFallback(); assert isFallback();
SwitchNode sw = (SwitchNode) insn; SwitchNode sw = (SwitchNode) insn;
@@ -505,6 +514,13 @@ public class InsnGen {
} }
private void fillArray(FillArrayNode insn, CodeWriter code) throws CodegenException { private void fillArray(FillArrayNode insn, CodeWriter code) throws CodegenException {
String filledArray = makeArrayElements(insn);
code.add("new ");
useType(code, insn.getElementType());
code.add("[]{").add(filledArray).add('}');
}
private String makeArrayElements(FillArrayNode insn) throws CodegenException {
ArgType insnArrayType = insn.getResult().getType(); ArgType insnArrayType = insn.getResult().getType();
ArgType insnElementType = insnArrayType.getArrayElement(); ArgType insnElementType = insnArrayType.getArrayElement();
ArgType elType = insn.getElementType(); ArgType elType = insn.getElementType();
@@ -513,10 +529,13 @@ public class InsnGen {
"Incorrect type for fill-array insn " + InsnUtils.formatOffset(insn.getOffset()) "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();
}
} }
if (!elType.isTypeKnown()) {
LOG.warn("Unknown array element type: {} in mth: {}", elType, mth);
elType = insnElementType.isTypeKnown() ? insnElementType : elType.selectFirst();
}
insn.mergeElementType(elType);
StringBuilder str = new StringBuilder(); StringBuilder str = new StringBuilder();
Object data = insn.getData(); Object data = insn.getData();
switch (elType.getPrimitiveType()) { switch (elType.getPrimitiveType()) {
@@ -558,9 +577,7 @@ public class InsnGen {
} }
int len = str.length(); int len = str.length();
str.delete(len - 2, len); str.delete(len - 2, len);
code.add("new "); return str.toString();
useType(code, elType);
code.add("[]{").add(str.toString()).add('}');
} }
private void makeConstructor(ConstructorInsn insn, CodeWriter code) private void makeConstructor(ConstructorInsn insn, CodeWriter code)
@@ -604,7 +621,7 @@ public class InsnGen {
code.add("new "); code.add("new ");
useClass(code, insn.getClassType()); useClass(code, insn.getClassType());
} }
generateArguments(code, insn, 0, mth.dex().resolveMethod(insn.getCallMth())); generateMethodArguments(code, insn, 0, mth.dex().resolveMethod(insn.getCallMth()));
} }
private void makeInvoke(InvokeNode insn, CodeWriter code) throws CodegenException { private void makeInvoke(InvokeNode insn, CodeWriter code) throws CodegenException {
@@ -649,47 +666,74 @@ public class InsnGen {
code.attachAnnotation(callMthNode); code.attachAnnotation(callMthNode);
} }
code.add(callMth.getName()); code.add(callMth.getName());
generateArguments(code, insn, k, callMthNode); generateMethodArguments(code, insn, k, callMthNode);
} }
private void generateArguments(CodeWriter code, InsnNode insn, int k, MethodNode callMth) throws CodegenException { private void generateMethodArguments(CodeWriter code, InsnNode insn, int startArgNum,
@Nullable MethodNode callMth) throws CodegenException {
int k = startArgNum;
if (callMth != null && callMth.contains(AFlag.SKIP_FIRST_ARG)) { if (callMth != null && callMth.contains(AFlag.SKIP_FIRST_ARG)) {
k++; k++;
} }
int argsCount = insn.getArgsCount(); int argsCount = insn.getArgsCount();
if (callMth != null && callMth.isArgsOverload()) { code.add('(');
// add additional argument casts for overloaded methods if (k < argsCount) {
List<ArgType> originalType = callMth.getMethodInfo().getArgumentsTypes(); boolean overloaded = callMth != null && callMth.isArgsOverload();
int origPos = 0;
code.add('(');
for (int i = k; i < argsCount; i++) { for (int i = k; i < argsCount; i++) {
InsnArg arg = insn.getArg(i); InsnArg arg = insn.getArg(i);
ArgType origType = originalType.get(origPos); boolean cast = overloaded && processOverloadedArg(code, callMth, arg, i - startArgNum);
if (!arg.getType().equals(origType)) { if (!cast && i == argsCount - 1 && processVarArg(code, callMth, arg)) {
code.add('('); continue;
useType(code, origType);
code.add(')');
addArg(code, arg, true);
} else {
addArg(code, arg, false);
} }
addArg(code, arg, false);
if (i < argsCount - 1) { if (i < argsCount - 1) {
code.add(", "); code.add(", ");
} }
origPos++;
} }
code.add(')'); }
} else { code.add(')');
}
/**
* Add additional cast for overloaded method argument.
*/
private boolean processOverloadedArg(CodeWriter code, MethodNode callMth, InsnArg arg, int origPos) {
ArgType origType = callMth.getMethodInfo().getArgumentsTypes().get(origPos);
if (!arg.getType().equals(origType)) {
code.add('('); code.add('(');
if (k < argsCount) { useType(code, origType);
addArg(code, insn.getArg(k), false); code.add(") ");
for (int i = k + 1; i < argsCount; i++) { return true;
}
return false;
}
/**
* Expand varArgs from filled array.
*/
private boolean processVarArg(CodeWriter code, MethodNode callMth, InsnArg lastArg) throws CodegenException {
if (callMth == null || !callMth.getAccessFlags().isVarArgs()) {
return false;
}
if (!lastArg.getType().isArray() || !lastArg.isInsnWrap()) {
return false;
}
InsnNode insn = ((InsnWrapArg) lastArg).getWrapInsn();
if (insn.getType() == InsnType.FILLED_NEW_ARRAY) {
int count = insn.getArgsCount();
for (int i = 0; i < count; i++) {
InsnArg elemArg = insn.getArg(i);
addArg(code, elemArg, false);
if (i < count - 1) {
code.add(", "); code.add(", ");
addArg(code, insn.getArg(i), false);
} }
} }
code.add(')'); return true;
} else if (insn.getType() == InsnType.FILL_ARRAY) {
code.add(makeArrayElements((FillArrayNode) insn));
return true;
} }
return false;
} }
private boolean inlineMethod(MethodNode callMthNode, InvokeNode insn, CodeWriter code) throws CodegenException { private boolean inlineMethod(MethodNode callMthNode, InvokeNode insn, CodeWriter code) throws CodegenException {
@@ -736,7 +780,7 @@ public class InsnGen {
return true; return true;
} }
private void makeTernary(TernaryInsn insn, CodeWriter code, EnumSet<Flags> state) throws CodegenException { private void makeTernary(TernaryInsn insn, CodeWriter code, Set<Flags> state) throws CodegenException {
boolean wrap = state.contains(Flags.BODY_ONLY); boolean wrap = state.contains(Flags.BODY_ONLY);
if (wrap) { if (wrap) {
code.add('('); code.add('(');
@@ -758,7 +802,11 @@ public class InsnGen {
} }
} }
private void makeArith(ArithNode insn, CodeWriter code, EnumSet<Flags> state) throws CodegenException { private void makeArith(ArithNode insn, CodeWriter code, Set<Flags> state) throws CodegenException {
if (insn.contains(AFlag.ARITH_ONEARG)) {
makeArithOneArg(insn, code);
return;
}
// wrap insn in brackets for save correct operation order // wrap insn in brackets for save correct operation order
boolean wrap = state.contains(Flags.BODY_ONLY) && !insn.contains(AFlag.DONT_WRAP); boolean wrap = state.contains(Flags.BODY_ONLY) && !insn.contains(AFlag.DONT_WRAP);
if (wrap) { if (wrap) {
@@ -776,7 +824,7 @@ public class InsnGen {
private void makeArithOneArg(ArithNode insn, CodeWriter code) throws CodegenException { private void makeArithOneArg(ArithNode insn, CodeWriter code) throws CodegenException {
ArithOp op = insn.getOp(); ArithOp op = insn.getOp();
InsnArg arg = insn.getArg(0); InsnArg arg = insn.getArg(1);
// "++" or "--" // "++" or "--"
if (arg.isLiteral() && (op == ArithOp.ADD || op == ArithOp.SUB)) { if (arg.isLiteral() && (op == ArithOp.ADD || op == ArithOp.SUB)) {
LiteralArg lit = (LiteralArg) arg; LiteralArg lit = (LiteralArg) arg;
@@ -78,8 +78,8 @@ public class MethodGen {
if (clsAccFlags.isAnnotation()) { if (clsAccFlags.isAnnotation()) {
ai = ai.remove(AccessFlags.ACC_PUBLIC); ai = ai.remove(AccessFlags.ACC_PUBLIC);
} }
code.startLine(ai.makeString()); code.startLineWithNum(mth.getSourceLine());
code.attachSourceLine(mth.getSourceLine()); code.add(ai.makeString());
if (classGen.addGenericMap(code, mth.getGenericMap())) { if (classGen.addGenericMap(code, mth.getGenericMap())) {
code.add(' '); code.add(' ');
@@ -206,13 +206,10 @@ public class MethodGen {
if (insn == null) { if (insn == null) {
continue; continue;
} }
if (addLabels) { if (addLabels && (insn.contains(AType.JUMP) || insn.contains(AType.EXC_HANDLER))) {
if (insn.contains(AType.JUMP) code.decIndent();
|| insn.contains(AType.EXC_HANDLER)) { code.startLine(getLabelName(insn.getOffset()) + ":");
code.decIndent(); code.incIndent();
code.startLine(getLabelName(insn.getOffset()) + ":");
code.incIndent();
}
} }
try { try {
if (insnGen.makeInsn(insn, code)) { if (insnGen.makeInsn(insn, code)) {
@@ -2,6 +2,7 @@ package jadx.core.codegen;
import jadx.core.Consts; import jadx.core.Consts;
import jadx.core.deobf.NameMapper; import jadx.core.deobf.NameMapper;
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
import jadx.core.dex.info.ClassInfo; import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.instructions.InvokeNode; import jadx.core.dex.instructions.InvokeNode;
import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.ArgType;
@@ -53,10 +54,7 @@ public class NameGen {
return name; return name;
} }
name = getUniqueVarName(name); name = getUniqueVarName(name);
SSAVar sVar = arg.getSVar(); arg.setName(name);
if (sVar != null) {
sVar.setName(name);
}
return name; return name;
} }
@@ -71,7 +69,16 @@ public class NameGen {
} }
public String useArg(RegisterArg arg) { public String useArg(RegisterArg arg) {
String name = makeArgName(arg); String name = arg.getName();
if (name == null) {
return getFallbackName(arg);
}
return name;
}
// TODO: avoid name collision with variables names
public String getLoopLabel(LoopLabelAttr attr) {
String name = "loop" + attr.getLoop().getId();
varNames.add(name); varNames.add(name);
return name; return name;
} }
@@ -88,14 +95,10 @@ public class NameGen {
} }
private String makeArgName(RegisterArg arg) { private String makeArgName(RegisterArg arg) {
String name = arg.getName();
if (fallback) { if (fallback) {
String base = "r" + arg.getRegNum(); return getFallbackName(arg);
if (name != null && !name.equals("this")) {
return base + "_" + name;
}
return base;
} }
String name = arg.getName();
String varName; String varName;
if (name != null) { if (name != null) {
if ("this".equals(name)) { if ("this".equals(name)) {
@@ -111,6 +114,15 @@ public class NameGen {
return varName; return varName;
} }
private String getFallbackName(RegisterArg arg) {
String name = arg.getName();
String base = "r" + arg.getRegNum();
if (name != null && !name.equals("this")) {
return base + "_" + name;
}
return base;
}
private static String makeNameForType(ArgType type) { private static String makeNameForType(ArgType type) {
if (type.isPrimitive()) { if (type.isPrimitive()) {
return makeNameForPrimitive(type); return makeNameForPrimitive(type);
@@ -4,27 +4,35 @@ import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.DeclareVariablesAttr; import jadx.core.dex.attributes.nodes.DeclareVariablesAttr;
import jadx.core.dex.attributes.nodes.ForceReturnAttr; import jadx.core.dex.attributes.nodes.ForceReturnAttr;
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
import jadx.core.dex.info.FieldInfo; import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.instructions.IndexInsnNode; import jadx.core.dex.instructions.IndexInsnNode;
import jadx.core.dex.instructions.SwitchNode; import jadx.core.dex.instructions.SwitchNode;
import jadx.core.dex.instructions.args.InsnArg; import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.NamedArg;
import jadx.core.dex.instructions.args.RegisterArg; import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.BlockNode; import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.IBlock; import jadx.core.dex.nodes.IBlock;
import jadx.core.dex.nodes.IContainer; import jadx.core.dex.nodes.IContainer;
import jadx.core.dex.nodes.IRegion; import jadx.core.dex.nodes.IRegion;
import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.regions.IfCondition;
import jadx.core.dex.regions.IfRegion;
import jadx.core.dex.regions.LoopRegion;
import jadx.core.dex.regions.Region; import jadx.core.dex.regions.Region;
import jadx.core.dex.regions.SwitchRegion; import jadx.core.dex.regions.SwitchRegion;
import jadx.core.dex.regions.SynchronizedRegion; import jadx.core.dex.regions.SynchronizedRegion;
import jadx.core.dex.trycatch.CatchAttr; import jadx.core.dex.regions.TryCatchRegion;
import jadx.core.dex.regions.conditions.IfCondition;
import jadx.core.dex.regions.conditions.IfRegion;
import jadx.core.dex.regions.loops.ForEachLoop;
import jadx.core.dex.regions.loops.ForLoop;
import jadx.core.dex.regions.loops.LoopRegion;
import jadx.core.dex.regions.loops.LoopType;
import jadx.core.dex.trycatch.ExceptionHandler; import jadx.core.dex.trycatch.ExceptionHandler;
import jadx.core.dex.trycatch.TryCatchBlock; import jadx.core.dex.trycatch.TryCatchBlock;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.RegionUtils; import jadx.core.utils.RegionUtils;
import jadx.core.utils.exceptions.CodegenException; import jadx.core.utils.exceptions.CodegenException;
import jadx.core.utils.exceptions.JadxRuntimeException;
import java.util.List; import java.util.List;
@@ -52,6 +60,8 @@ public class RegionGen extends InsnGen {
makeSwitch((SwitchRegion) cont, code); makeSwitch((SwitchRegion) cont, code);
} else if (cont instanceof LoopRegion) { } else if (cont instanceof LoopRegion) {
makeLoop((LoopRegion) cont, code); makeLoop((LoopRegion) cont, code);
} else if (cont instanceof TryCatchRegion) {
makeTryCatch((TryCatchRegion) cont, code);
} else if (cont instanceof SynchronizedRegion) { } else if (cont instanceof SynchronizedRegion) {
makeSynchronizedRegion((SynchronizedRegion) cont, code); makeSynchronizedRegion((SynchronizedRegion) cont, code);
} }
@@ -73,14 +83,9 @@ public class RegionGen extends InsnGen {
} }
private void makeSimpleRegion(CodeWriter code, Region region) throws CodegenException { private void makeSimpleRegion(CodeWriter code, Region region) throws CodegenException {
CatchAttr tc = region.get(AType.CATCH_BLOCK); declareVars(code, region);
if (tc != null) { for (IContainer c : region.getSubBlocks()) {
makeTryCatch(region, tc.getTryBlock(), code); makeRegion(code, c);
} else {
declareVars(code, region);
for (IContainer c : region.getSubBlocks()) {
makeRegion(code, c);
}
} }
} }
@@ -92,7 +97,9 @@ public class RegionGen extends InsnGen {
private void makeSimpleBlock(IBlock block, CodeWriter code) throws CodegenException { private void makeSimpleBlock(IBlock block, CodeWriter code) throws CodegenException {
for (InsnNode insn : block.getInstructions()) { for (InsnNode insn : block.getInstructions()) {
makeInsn(insn, code); if (!insn.contains(AFlag.SKIP)) {
makeInsn(insn, code);
}
} }
ForceReturnAttr retAttr = block.get(AType.FORCE_RETURN); ForceReturnAttr retAttr = block.get(AType.FORCE_RETURN);
if (retAttr != null) { if (retAttr != null) {
@@ -101,14 +108,11 @@ public class RegionGen extends InsnGen {
} }
private void makeIf(IfRegion region, CodeWriter code, boolean newLine) throws CodegenException { private void makeIf(IfRegion region, CodeWriter code, boolean newLine) throws CodegenException {
if (region.getTernRegion() != null) {
makeSimpleBlock(region.getTernRegion().getBlock(), code);
return;
}
if (newLine) { if (newLine) {
code.startLine(); code.startLineWithNum(region.getSourceLine());
} else {
code.attachSourceLine(region.getSourceLine());
} }
code.attachSourceLine(region.getSourceLine());
code.add("if ("); code.add("if (");
new ConditionGen(this).add(code, region.getCondition()); new ConditionGen(this).add(code, region.getCondition());
code.add(") {"); code.add(") {");
@@ -151,8 +155,7 @@ public class RegionGen extends InsnGen {
if (header != null) { if (header != null) {
List<InsnNode> headerInsns = header.getInstructions(); List<InsnNode> headerInsns = header.getInstructions();
if (headerInsns.size() > 1) { if (headerInsns.size() > 1) {
// write not inlined instructions from header ErrorsCounter.methodError(mth, "Found not inlined instructions from loop header");
mth.add(AFlag.INCONSISTENT_CODE);
int last = headerInsns.size() - 1; int last = headerInsns.size() - 1;
for (int i = 0; i < last; i++) { for (int i = 0; i < last; i++) {
InsnNode insn = headerInsns.get(i); InsnNode insn = headerInsns.get(i);
@@ -160,6 +163,10 @@ public class RegionGen extends InsnGen {
} }
} }
} }
LoopLabelAttr labelAttr = region.getInfo().getStart().get(AType.LOOP_LABEL);
if (labelAttr != null) {
code.startLine(mgen.getNameGen().getLoopLabel(labelAttr)).add(':');
}
IfCondition condition = region.getCondition(); IfCondition condition = region.getCondition();
if (condition == null) { if (condition == null) {
@@ -169,8 +176,35 @@ public class RegionGen extends InsnGen {
code.startLine('}'); code.startLine('}');
return code; return code;
} }
ConditionGen conditionGen = new ConditionGen(this); ConditionGen conditionGen = new ConditionGen(this);
LoopType type = region.getType();
if (type != null) {
if (type instanceof ForLoop) {
ForLoop forLoop = (ForLoop) type;
code.startLine("for (");
makeInsn(forLoop.getInitInsn(), code, Flags.INLINE);
code.add("; ");
conditionGen.add(code, condition);
code.add("; ");
makeInsn(forLoop.getIncrInsn(), code, Flags.INLINE);
code.add(") {");
makeRegionIndent(code, region.getBody());
code.startLine('}');
return code;
}
if (type instanceof ForEachLoop) {
ForEachLoop forEachLoop = (ForEachLoop) type;
code.startLine("for (");
declareVar(code, forEachLoop.getVarArg());
code.add(" : ");
addArg(code, forEachLoop.getIterableArg(), false);
code.add(") {");
makeRegionIndent(code, region.getBody());
code.startLine('}');
return code;
}
throw new JadxRuntimeException("Unknown loop type: " + type.getClass());
}
if (region.isConditionAtEnd()) { if (region.isConditionAtEnd()) {
code.startLine("do {"); code.startLine("do {");
makeRegionIndent(code, region.getBody()); makeRegionIndent(code, region.getBody());
@@ -209,7 +243,14 @@ public class RegionGen extends InsnGen {
IContainer c = sw.getCases().get(i); IContainer c = sw.getCases().get(i);
for (Object k : keys) { for (Object k : keys) {
code.startLine("case "); code.startLine("case ");
if (k instanceof IndexInsnNode) { if (k instanceof FieldNode) {
FieldNode fn = (FieldNode) k;
if (fn.getParentClass().isEnum()) {
code.add(fn.getName());
} else {
staticField(code, fn.getFieldInfo());
}
} else if (k instanceof IndexInsnNode) {
staticField(code, (FieldInfo) ((IndexInsnNode) k).getIndex()); staticField(code, (FieldInfo) ((IndexInsnNode) k).getIndex());
} else { } else {
code.add(TypeGen.literalToString((Integer) k, arg.getType())); code.add(TypeGen.literalToString((Integer) k, arg.getType()));
@@ -240,18 +281,18 @@ public class RegionGen extends InsnGen {
} }
} }
private void makeTryCatch(IContainer region, TryCatchBlock tryCatchBlock, CodeWriter code) private void makeTryCatch(TryCatchRegion region, CodeWriter code) throws CodegenException {
throws CodegenException { TryCatchBlock tryCatchBlock = region.geTryCatchBlock();
code.startLine("try {"); code.startLine("try {");
region.remove(AType.CATCH_BLOCK); makeRegionIndent(code, region.getTryRegion());
makeRegionIndent(code, region); // TODO: move search of 'allHandler' to 'TryCatchRegion'
ExceptionHandler allHandler = null; ExceptionHandler allHandler = null;
for (ExceptionHandler handler : tryCatchBlock.getHandlers()) { for (ExceptionHandler handler : tryCatchBlock.getHandlers()) {
if (!handler.isCatchAll()) { if (!handler.isCatchAll()) {
makeCatchBlock(code, handler); makeCatchBlock(code, handler);
} else { } else {
if (allHandler != null) { if (allHandler != null) {
LOG.warn("Several 'all' handlers in try/catch block in " + mth); LOG.warn("Several 'all' handlers in try/catch block in {}", mth);
} }
allHandler = handler; allHandler = handler;
} }
@@ -266,20 +307,25 @@ public class RegionGen extends InsnGen {
code.startLine('}'); code.startLine('}');
} }
private void makeCatchBlock(CodeWriter code, ExceptionHandler handler) private void makeCatchBlock(CodeWriter code, ExceptionHandler handler) throws CodegenException {
throws CodegenException {
IContainer region = handler.getHandlerRegion(); IContainer region = handler.getHandlerRegion();
if (region != null) { if (region == null) {
code.startLine("} catch ("); return;
}
code.startLine("} catch (");
InsnArg arg = handler.getArg();
if (arg instanceof RegisterArg) {
declareVar(code, (RegisterArg) arg);
} else if (arg instanceof NamedArg) {
if (handler.isCatchAll()) { if (handler.isCatchAll()) {
code.add("Throwable"); code.add("Throwable");
} else { } else {
useClass(code, handler.getCatchType()); useClass(code, handler.getCatchType());
} }
code.add(' '); code.add(' ');
code.add(mgen.getNameGen().assignNamedArg(handler.getArg())); code.add(mgen.getNameGen().assignNamedArg((NamedArg) arg));
code.add(") {");
makeRegionIndent(code, region);
} }
code.add(") {");
makeRegionIndent(code, region);
} }
} }
@@ -6,7 +6,14 @@ import jadx.core.utils.StringUtils;
import jadx.core.utils.Utils; import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxRuntimeException; import jadx.core.utils.exceptions.JadxRuntimeException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class TypeGen { public class TypeGen {
private static final Logger LOG = LoggerFactory.getLogger(TypeGen.class);
private TypeGen() {
}
public static String signature(ArgType type) { public static String signature(ArgType type) {
PrimitiveType stype = type.getPrimitiveType(); PrimitiveType stype = type.getPrimitiveType();
@@ -56,7 +63,8 @@ public class TypeGen {
case OBJECT: case OBJECT:
case ARRAY: case ARRAY:
if (lit != 0) { if (lit != 0) {
throw new JadxRuntimeException("Wrong object literal: " + type + " = " + lit); LOG.warn("Wrong object literal: " + lit + " for type: " + type);
return Long.toString(lit);
} }
return "null"; return "null";
@@ -15,6 +15,7 @@ public enum AFlag {
DONT_WRAP, DONT_WRAP,
DONT_SHRINK, DONT_SHRINK,
DONT_INLINE,
DONT_GENERATE, DONT_GENERATE,
SKIP, SKIP,
@@ -23,5 +24,8 @@ public enum AFlag {
ELSE_IF_CHAIN, ELSE_IF_CHAIN,
WRAPPED,
ARITH_ONEARG,
INCONSISTENT_CODE, // warning about incorrect decompilation INCONSISTENT_CODE, // warning about incorrect decompilation
} }
@@ -4,11 +4,13 @@ import jadx.core.dex.attributes.annotations.AnnotationsList;
import jadx.core.dex.attributes.annotations.MethodParameters; import jadx.core.dex.attributes.annotations.MethodParameters;
import jadx.core.dex.attributes.nodes.DeclareVariablesAttr; import jadx.core.dex.attributes.nodes.DeclareVariablesAttr;
import jadx.core.dex.attributes.nodes.EnumClassAttr; import jadx.core.dex.attributes.nodes.EnumClassAttr;
import jadx.core.dex.attributes.nodes.EnumMapAttr;
import jadx.core.dex.attributes.nodes.FieldReplaceAttr; import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
import jadx.core.dex.attributes.nodes.ForceReturnAttr; import jadx.core.dex.attributes.nodes.ForceReturnAttr;
import jadx.core.dex.attributes.nodes.JadxErrorAttr; import jadx.core.dex.attributes.nodes.JadxErrorAttr;
import jadx.core.dex.attributes.nodes.JumpInfo; import jadx.core.dex.attributes.nodes.JumpInfo;
import jadx.core.dex.attributes.nodes.LoopInfo; import jadx.core.dex.attributes.nodes.LoopInfo;
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
import jadx.core.dex.attributes.nodes.MethodInlineAttr; import jadx.core.dex.attributes.nodes.MethodInlineAttr;
import jadx.core.dex.attributes.nodes.PhiListAttr; import jadx.core.dex.attributes.nodes.PhiListAttr;
import jadx.core.dex.attributes.nodes.SourceFileAttr; import jadx.core.dex.attributes.nodes.SourceFileAttr;
@@ -28,6 +30,8 @@ public class AType<T extends IAttribute> {
private AType() { private AType() {
} }
public static final int FIELDS_COUNT = 18;
public static final AType<AttrList<JumpInfo>> JUMP = new AType<AttrList<JumpInfo>>(); public static final AType<AttrList<JumpInfo>> JUMP = new AType<AttrList<JumpInfo>>();
public static final AType<AttrList<LoopInfo>> LOOP = new AType<AttrList<LoopInfo>>(); public static final AType<AttrList<LoopInfo>> LOOP = new AType<AttrList<LoopInfo>>();
@@ -40,9 +44,11 @@ public class AType<T extends IAttribute> {
public static final AType<JadxErrorAttr> JADX_ERROR = new AType<JadxErrorAttr>(); public static final AType<JadxErrorAttr> JADX_ERROR = new AType<JadxErrorAttr>();
public static final AType<MethodInlineAttr> METHOD_INLINE = new AType<MethodInlineAttr>(); public static final AType<MethodInlineAttr> METHOD_INLINE = new AType<MethodInlineAttr>();
public static final AType<EnumClassAttr> ENUM_CLASS = new AType<EnumClassAttr>(); public static final AType<EnumClassAttr> ENUM_CLASS = new AType<EnumClassAttr>();
public static final AType<EnumMapAttr> ENUM_MAP = new AType<EnumMapAttr>();
public static final AType<AnnotationsList> ANNOTATION_LIST = new AType<AnnotationsList>(); public static final AType<AnnotationsList> ANNOTATION_LIST = new AType<AnnotationsList>();
public static final AType<MethodParameters> ANNOTATION_MTH_PARAMETERS = new AType<MethodParameters>(); public static final AType<MethodParameters> ANNOTATION_MTH_PARAMETERS = new AType<MethodParameters>();
public static final AType<PhiListAttr> PHI_LIST = new AType<PhiListAttr>(); public static final AType<PhiListAttr> PHI_LIST = new AType<PhiListAttr>();
public static final AType<SourceFileAttr> SOURCE_FILE = new AType<SourceFileAttr>(); public static final AType<SourceFileAttr> SOURCE_FILE = new AType<SourceFileAttr>();
public static final AType<DeclareVariablesAttr> DECLARE_VARIABLES = new AType<DeclareVariablesAttr>(); public static final AType<DeclareVariablesAttr> DECLARE_VARIABLES = new AType<DeclareVariablesAttr>();
public static final AType<LoopLabelAttr> LOOP_LABEL = new AType<LoopLabelAttr>();
} }
@@ -12,25 +12,25 @@ public abstract class AttrNode implements IAttributeNode {
@Override @Override
public void add(AFlag flag) { public void add(AFlag flag) {
getStorage().add(flag); initStorage().add(flag);
} }
@Override @Override
public void addAttr(IAttribute attr) { public void addAttr(IAttribute attr) {
getStorage().add(attr); initStorage().add(attr);
} }
@Override @Override
public <T> void addAttr(AType<AttrList<T>> type, T obj) { public <T> void addAttr(AType<AttrList<T>> type, T obj) {
getStorage().add(type, obj); initStorage().add(type, obj);
} }
@Override @Override
public void copyAttributesFrom(AttrNode attrNode) { public void copyAttributesFrom(AttrNode attrNode) {
getStorage().addAll(attrNode.storage); initStorage().addAll(attrNode.storage);
} }
AttributeStorage getStorage() { AttributeStorage initStorage() {
AttributeStorage store = storage; AttributeStorage store = storage;
if (store == EMPTY_ATTR_STORAGE) { if (store == EMPTY_ATTR_STORAGE) {
store = new AttributeStorage(); store = new AttributeStorage();
@@ -7,7 +7,7 @@ import jadx.core.utils.Utils;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.HashMap; import java.util.IdentityHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@@ -24,7 +24,7 @@ public class AttributeStorage {
public AttributeStorage() { public AttributeStorage() {
flags = EnumSet.noneOf(AFlag.class); flags = EnumSet.noneOf(AFlag.class);
attributes = new HashMap<AType<?>, IAttribute>(2); attributes = new IdentityHashMap<AType<?>, IAttribute>(AType.FIELDS_COUNT);
} }
public void add(AFlag flag) { public void add(AFlag flag) {
@@ -72,7 +72,7 @@ public class AttributeStorage {
if (attrList == null) { if (attrList == null) {
return Collections.emptyList(); return Collections.emptyList();
} }
return attrList.getList(); return Collections.unmodifiableList(attrList.getList());
} }
public void remove(AFlag flag) { public void remove(AFlag flag) {
@@ -5,7 +5,7 @@ import jadx.core.dex.attributes.annotations.Annotation;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
public class EmptyAttrStorage extends AttributeStorage { public final class EmptyAttrStorage extends AttributeStorage {
@Override @Override
public boolean contains(AFlag flag) { public boolean contains(AFlag flag) {
@@ -52,4 +52,9 @@ public class EmptyAttrStorage extends AttributeStorage {
public List<String> getAttributeStrings() { public List<String> getAttributeStrings() {
return Collections.emptyList(); return Collections.emptyList();
} }
@Override
public String toString() {
return "";
}
} }
@@ -32,6 +32,10 @@ public class AnnotationsList implements IAttribute {
return map.size(); return map.size();
} }
public boolean isEmpty() {
return map.isEmpty();
}
@Override @Override
public AType<AnnotationsList> getType() { public AType<AnnotationsList> getType() {
return AType.ANNOTATION_LIST; return AType.ANNOTATION_LIST;
@@ -0,0 +1,49 @@
package jadx.core.dex.attributes.nodes;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.nodes.FieldNode;
import java.util.HashMap;
import java.util.Map;
public class EnumMapAttr implements IAttribute {
public static class KeyValueMap {
private Map<Object, Object> map = new HashMap<Object, Object>();
public Object get(Object key) {
return map.get(key);
}
void put(Object key, Object value) {
map.put(key, value);
}
}
private Map<FieldNode, KeyValueMap> fieldsMap = new HashMap<FieldNode, KeyValueMap>();
public KeyValueMap getMap(FieldNode field) {
return fieldsMap.get(field);
}
public void add(FieldNode field, Object key, Object value) {
KeyValueMap map = getMap(field);
if (map == null) {
map = new KeyValueMap();
fieldsMap.put(field, map);
}
map.put(key, value);
}
@Override
public AType<EnumMapAttr> getType() {
return AType.ENUM_MAP;
}
@Override
public String toString() {
return "Enum fields map: " + fieldsMap;
}
}
@@ -17,6 +17,9 @@ public class LoopInfo {
private final BlockNode end; private final BlockNode end;
private final Set<BlockNode> loopBlocks; private final Set<BlockNode> loopBlocks;
private int id;
private LoopInfo parentLoop;
public LoopInfo(BlockNode start, BlockNode end) { public LoopInfo(BlockNode start, BlockNode end) {
this.start = start; this.start = start;
this.end = end; this.end = end;
@@ -69,8 +72,24 @@ public class LoopInfo {
return edges; return edges;
} }
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public LoopInfo getParentLoop() {
return parentLoop;
}
public void setParentLoop(LoopInfo parentLoop) {
this.parentLoop = parentLoop;
}
@Override @Override
public String toString() { public String toString() {
return "LOOP: " + start + "->" + end; return "LOOP:" + id + ": " + start + "->" + end;
} }
} }
@@ -0,0 +1,27 @@
package jadx.core.dex.attributes.nodes;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
public class LoopLabelAttr implements IAttribute {
private final LoopInfo loop;
public LoopLabelAttr(LoopInfo loop) {
this.loop = loop;
}
public LoopInfo getLoop() {
return loop;
}
@Override
public AType<LoopLabelAttr> getType() {
return AType.LOOP_LABEL;
}
@Override
public String toString() {
return "LOOP_LABEL: " + loop;
}
}
@@ -1,5 +1,6 @@
package jadx.core.dex.instructions; package jadx.core.dex.instructions;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg; import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.RegisterArg; import jadx.core.dex.instructions.args.RegisterArg;
@@ -51,10 +52,8 @@ public class ArithNode extends InsnNode {
} }
public ArithNode(ArithOp op, RegisterArg res, InsnArg a) { public ArithNode(ArithOp op, RegisterArg res, InsnArg a) {
super(InsnType.ARITH_ONEARG, 1); this(op, res, res, a);
this.op = op; add(AFlag.ARITH_ONEARG);
setResult(res);
addArg(a);
} }
public ArithOp getOp() { public ArithOp getOp() {
@@ -85,7 +84,7 @@ public class ArithNode extends InsnNode {
+ getResult() + " = " + getResult() + " = "
+ getArg(0) + " " + getArg(0) + " "
+ op.getSymbol() + " " + op.getSymbol() + " "
+ (getArgsCount() == 2 ? getArg(1) : ""); + getArg(1);
} }
} }
@@ -4,16 +4,14 @@ import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.info.MethodInfo; import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg; import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.NamedArg;
import jadx.core.dex.instructions.args.PrimitiveType; import jadx.core.dex.instructions.args.PrimitiveType;
import jadx.core.dex.instructions.args.RegisterArg; import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.DexNode; import jadx.core.dex.nodes.DexNode;
import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.InsnUtils;
import jadx.core.utils.exceptions.DecodeException; import jadx.core.utils.exceptions.DecodeException;
import java.io.EOFException;
import com.android.dex.Code; import com.android.dex.Code;
import com.android.dx.io.OpcodeInfo; import com.android.dx.io.OpcodeInfo;
import com.android.dx.io.Opcodes; import com.android.dx.io.Opcodes;
@@ -44,7 +42,7 @@ public class InsnDecoder {
while (in.hasMore()) { while (in.hasMore()) {
decoded[in.cursor()] = DecodedInstruction.decode(in); decoded[in.cursor()] = DecodedInstruction.decode(in);
} }
} catch (EOFException e) { } catch (Exception e) {
throw new DecodeException(method, "", e); throw new DecodeException(method, "", e);
} }
insnArr = decoded; insnArr = decoded;
@@ -406,8 +404,7 @@ public class InsnDecoder {
case Opcodes.MOVE_EXCEPTION: case Opcodes.MOVE_EXCEPTION:
return insn(InsnType.MOVE_EXCEPTION, return insn(InsnType.MOVE_EXCEPTION,
InsnArg.reg(insn, 0, ArgType.unknown(PrimitiveType.OBJECT)), InsnArg.reg(insn, 0, ArgType.unknown(PrimitiveType.OBJECT)));
new NamedArg("e", ArgType.unknown(PrimitiveType.OBJECT)));
case Opcodes.RETURN_VOID: case Opcodes.RETURN_VOID:
return new InsnNode(InsnType.RETURN, 0); return new InsnNode(InsnType.RETURN, 0);
@@ -624,16 +621,19 @@ public class InsnDecoder {
int resReg = getMoveResultRegister(insnArr, offset); int resReg = getMoveResultRegister(insnArr, offset);
ArgType arrType = dex.getType(insn.getIndex()); ArgType arrType = dex.getType(insn.getIndex());
ArgType elType = arrType.getArrayElement(); ArgType elType = arrType.getArrayElement();
InsnArg[] regs = new InsnArg[insn.getRegisterCount()]; boolean typeImmutable = elType.isPrimitive();
int regsCount = insn.getRegisterCount();
InsnArg[] regs = new InsnArg[regsCount];
if (isRange) { if (isRange) {
int r = insn.getA(); int r = insn.getA();
for (int i = 0; i < insn.getRegisterCount(); i++) { for (int i = 0; i < regsCount; i++) {
regs[i] = InsnArg.reg(r, elType); regs[i] = InsnArg.reg(r, elType, typeImmutable);
r++; r++;
} }
} else { } else {
for (int i = 0; i < insn.getRegisterCount(); i++) { for (int i = 0; i < regsCount; i++) {
regs[i] = InsnArg.reg(insn, i, elType); int regNum = InsnUtils.getArg(insn, i);
regs[i] = InsnArg.reg(regNum, elType, typeImmutable);
} }
} }
return insn(InsnType.FILLED_NEW_ARRAY, return insn(InsnType.FILLED_NEW_ARRAY,
@@ -1,7 +1,6 @@
package jadx.core.dex.instructions; package jadx.core.dex.instructions;
public enum InsnType { public enum InsnType {
NOP, // replacement for removed instructions
CONST, CONST,
CONST_STR, CONST_STR,
@@ -48,17 +47,24 @@ public enum InsnType {
INVOKE, INVOKE,
// additional instructions // *** Additional instructions ***
// replacement for removed instructions
NOP,
TERNARY,
CONSTRUCTOR, CONSTRUCTOR,
BREAK, BREAK,
CONTINUE, CONTINUE,
STR_CONCAT, // strings concatenation // strings concatenation
ARITH_ONEARG, STR_CONCAT,
TERNARY, // just generate one argument
ARGS, // just generate arguments ONE_ARG,
PHI, PHI,
NEW_MULTIDIM_ARRAY // TODO: now multidimensional arrays created using Array.newInstance function // TODO: now multidimensional arrays created using Array.newInstance function
NEW_MULTIDIM_ARRAY
} }
@@ -1,5 +1,6 @@
package jadx.core.dex.instructions; package jadx.core.dex.instructions;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg; import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.RegisterArg; import jadx.core.dex.instructions.args.RegisterArg;
@@ -14,6 +15,7 @@ public class PhiInsn extends InsnNode {
for (int i = 0; i < predecessors; i++) { for (int i = 0; i < predecessors; i++) {
addReg(regNum, ArgType.UNKNOWN); addReg(regNum, ArgType.UNKNOWN);
} }
add(AFlag.DONT_INLINE);
} }
@Override @Override
@@ -21,6 +23,14 @@ public class PhiInsn extends InsnNode {
return (RegisterArg) super.getArg(n); return (RegisterArg) super.getArg(n);
} }
public boolean removeArg(RegisterArg arg) {
boolean isRemoved = super.removeArg(arg);
if (isRemoved) {
arg.getSVar().setUsedInPhi(null);
}
return isRemoved;
}
@Override @Override
public String toString() { public String toString() {
return "PHI: " + getResult() + " = " + Utils.listToString(getArguments()); return "PHI: " + getResult() + " = " + Utils.listToString(getArguments());
@@ -9,6 +9,8 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import org.jetbrains.annotations.Nullable;
public abstract class ArgType { public abstract class ArgType {
public static final ArgType INT = primitive(PrimitiveType.INT); public static final ArgType INT = primitive(PrimitiveType.INT);
@@ -93,10 +95,28 @@ public abstract class ArgType {
} }
private abstract static class KnownType extends ArgType { private abstract static class KnownType extends ArgType {
private static final PrimitiveType[] EMPTY_POSSIBLES = new PrimitiveType[0];
@Override @Override
public boolean isTypeKnown() { public boolean isTypeKnown() {
return true; return true;
} }
@Override
public boolean contains(PrimitiveType type) {
return getPrimitiveType() == type;
}
@Override
public ArgType selectFirst() {
return null;
}
@Override
public PrimitiveType[] getPossibleTypes() {
return EMPTY_POSSIBLES;
}
} }
private static final class PrimitiveArg extends KnownType { private static final class PrimitiveArg extends KnownType {
@@ -269,6 +289,7 @@ public abstract class ArgType {
} }
private static final class ArrayArg extends KnownType { private static final class ArrayArg extends KnownType {
public static final PrimitiveType[] ARRAY_POSSIBLES = new PrimitiveType[]{PrimitiveType.ARRAY};
private final ArgType arrayElement; private final ArgType arrayElement;
public ArrayArg(ArgType arrayElement) { public ArrayArg(ArgType arrayElement) {
@@ -291,6 +312,21 @@ public abstract class ArgType {
return PrimitiveType.ARRAY; return PrimitiveType.ARRAY;
} }
@Override
public boolean isTypeKnown() {
return arrayElement.isTypeKnown();
}
@Override
public ArgType selectFirst() {
return array(arrayElement.selectFirst());
}
@Override
public PrimitiveType[] getPossibleTypes() {
return ARRAY_POSSIBLES;
}
@Override @Override
public int getArrayDimension() { public int getArrayDimension() {
return 1 + arrayElement.getArrayDimension(); return 1 + arrayElement.getArrayDimension();
@@ -343,8 +379,10 @@ public abstract class ArgType {
@Override @Override
public ArgType selectFirst() { public ArgType selectFirst() {
PrimitiveType f = possibleTypes[0]; PrimitiveType f = possibleTypes[0];
if (f == PrimitiveType.OBJECT || f == PrimitiveType.ARRAY) { if (contains(PrimitiveType.OBJECT)) {
return object(Consts.CLASS_OBJECT); return OBJECT;
} else if (contains(PrimitiveType.ARRAY)) {
return array(OBJECT);
} else { } else {
return primitive(f); return primitive(f);
} }
@@ -428,18 +466,13 @@ public abstract class ArgType {
return this; return this;
} }
public boolean contains(PrimitiveType type) { public abstract boolean contains(PrimitiveType type);
throw new UnsupportedOperationException();
}
public ArgType selectFirst() { public abstract ArgType selectFirst();
throw new UnsupportedOperationException();
}
public PrimitiveType[] getPossibleTypes() { public abstract PrimitiveType[] getPossibleTypes();
return null;
}
@Nullable
public static ArgType merge(ArgType a, ArgType b) { public static ArgType merge(ArgType a, ArgType b) {
if (a == null || b == null) { if (a == null || b == null) {
return null; return null;
@@ -458,13 +491,18 @@ public abstract class ArgType {
if (a == UNKNOWN) { if (a == UNKNOWN) {
return b; return b;
} }
if (a.isArray()) {
return mergeArrays((ArrayArg) a, b);
} else if (b.isArray()) {
return mergeArrays((ArrayArg) b, a);
}
if (!a.isTypeKnown()) { if (!a.isTypeKnown()) {
if (b.isTypeKnown()) { if (b.isTypeKnown()) {
if (a.contains(b.getPrimitiveType())) { if (a.contains(b.getPrimitiveType())) {
return b; return b;
} else {
return null;
} }
return null;
} else { } else {
// both types unknown // both types unknown
List<PrimitiveType> types = new ArrayList<PrimitiveType>(); List<PrimitiveType> types = new ArrayList<PrimitiveType>();
@@ -475,7 +513,8 @@ public abstract class ArgType {
} }
if (types.isEmpty()) { if (types.isEmpty()) {
return null; return null;
} else if (types.size() == 1) { }
if (types.size() == 1) {
PrimitiveType nt = types.get(0); PrimitiveType nt = types.get(0);
if (nt == PrimitiveType.OBJECT || nt == PrimitiveType.ARRAY) { if (nt == PrimitiveType.OBJECT || nt == PrimitiveType.ARRAY) {
return unknown(nt); return unknown(nt);
@@ -499,31 +538,15 @@ public abstract class ArgType {
String bObj = b.getObject(); String bObj = b.getObject();
if (aObj.equals(bObj)) { if (aObj.equals(bObj)) {
return a.getGenericTypes() != null ? a : b; return a.getGenericTypes() != null ? a : b;
} else if (aObj.equals(Consts.CLASS_OBJECT)) { }
if (aObj.equals(Consts.CLASS_OBJECT)) {
return b; return b;
} else if (bObj.equals(Consts.CLASS_OBJECT)) { }
if (bObj.equals(Consts.CLASS_OBJECT)) {
return a; return a;
} else {
// different objects
String obj = clsp.getCommonAncestor(aObj, bObj);
return obj == null ? null : object(obj);
}
}
if (a.isArray()) {
if (b.isArray()) {
ArgType ea = a.getArrayElement();
ArgType eb = b.getArrayElement();
if (ea.isPrimitive() && eb.isPrimitive()) {
return OBJECT;
} else {
ArgType res = merge(ea, eb);
return res == null ? null : array(res);
}
} else if (b.equals(OBJECT)) {
return OBJECT;
} else {
return null;
} }
String obj = clsp.getCommonAncestor(aObj, bObj);
return obj == null ? null : object(obj);
} }
if (a.isPrimitive() && b.isPrimitive() && a.getRegCount() == b.getRegCount()) { if (a.isPrimitive() && b.isPrimitive() && a.getRegCount() == b.getRegCount()) {
return primitive(PrimitiveType.getSmaller(a.getPrimitiveType(), b.getPrimitiveType())); return primitive(PrimitiveType.getSmaller(a.getPrimitiveType(), b.getPrimitiveType()));
@@ -532,6 +555,25 @@ public abstract class ArgType {
return null; return null;
} }
private static ArgType mergeArrays(ArrayArg array, ArgType b) {
if (b.isArray()) {
ArgType ea = array.getArrayElement();
ArgType eb = b.getArrayElement();
if (ea.isPrimitive() && eb.isPrimitive()) {
return OBJECT;
}
ArgType res = merge(ea, eb);
return res == null ? null : array(res);
}
if (b.contains(PrimitiveType.ARRAY)) {
return array;
}
if (b.equals(OBJECT)) {
return OBJECT;
}
return null;
}
public static boolean isCastNeeded(ArgType from, ArgType to) { public static boolean isCastNeeded(ArgType from, ArgType to) {
if (from.equals(to)) { if (from.equals(to)) {
return false; return false;
@@ -543,6 +585,16 @@ public abstract class ArgType {
return true; return true;
} }
public static boolean isInstanceOf(ArgType type, ArgType of) {
if (type.equals(of)) {
return true;
}
if (!type.isObject() || !of.isObject()) {
return false;
}
return clsp.isImplements(type.getObject(), of.getObject());
}
public static ArgType parse(String type) { public static ArgType parse(String type) {
char f = type.charAt(0); char f = type.charAt(0);
switch (f) { switch (f) {
@@ -1,8 +1,12 @@
package jadx.core.dex.instructions.args; package jadx.core.dex.instructions.args;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.InsnNode;
import jadx.core.utils.InsnUtils; import jadx.core.utils.InsnUtils;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -26,8 +30,12 @@ public abstract class InsnArg extends Typed {
return reg(InsnUtils.getArg(insn, argNum), type); return reg(InsnUtils.getArg(insn, argNum), type);
} }
public static MthParameterArg parameterReg(int regNum, ArgType type) { public static TypeImmutableArg typeImmutableReg(int regNum, ArgType type) {
return new MthParameterArg(regNum, type); return new TypeImmutableArg(regNum, type);
}
public static RegisterArg reg(int regNum, ArgType type, boolean typeImmutable) {
return typeImmutable ? new TypeImmutableArg(regNum, type) : new RegisterArg(regNum, type);
} }
public static LiteralArg lit(long literal, ArgType type) { public static LiteralArg lit(long literal, ArgType type) {
@@ -76,18 +84,36 @@ public abstract class InsnArg extends Typed {
return null; return null;
} }
if (parent == insn) { if (parent == insn) {
LOG.debug("Can't wrap instruction info itself: " + insn); LOG.debug("Can't wrap instruction info itself: {}", insn);
Thread.dumpStack();
return null; return null;
} }
int i = getArgIndex(parent, this);
if (i == -1) {
return null;
}
insn.add(AFlag.WRAPPED);
InsnArg arg = wrapArg(insn);
parent.setArg(i, arg);
return arg;
}
public static void updateParentInsn(InsnNode fromInsn, InsnNode toInsn) {
List<RegisterArg> args = new ArrayList<RegisterArg>();
fromInsn.getRegisterArgs(args);
for (RegisterArg reg : args) {
reg.setParentInsn(toInsn);
}
}
private static int getArgIndex(InsnNode parent, InsnArg arg) {
int count = parent.getArgsCount(); int count = parent.getArgsCount();
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
if (parent.getArg(i) == this) { if (parent.getArg(i) == arg) {
InsnArg arg = wrapArg(insn); return i;
parent.setArg(i, arg);
return arg;
} }
} }
return null; return -1;
} }
public static InsnArg wrapArg(InsnNode insn) { public static InsnArg wrapArg(InsnNode insn) {
@@ -38,11 +38,11 @@ public final class LiteralArg extends InsnArg {
public boolean isInteger() { public boolean isInteger() {
PrimitiveType type = this.type.getPrimitiveType(); PrimitiveType type = this.type.getPrimitiveType();
return (type == PrimitiveType.INT return type == PrimitiveType.INT
|| type == PrimitiveType.BYTE || type == PrimitiveType.BYTE
|| type == PrimitiveType.CHAR || type == PrimitiveType.CHAR
|| type == PrimitiveType.SHORT || type == PrimitiveType.SHORT
|| type == PrimitiveType.LONG); || type == PrimitiveType.LONG;
} }
@Override @Override
@@ -19,7 +19,8 @@ public class RegisterArg extends InsnArg implements Named {
private static final Logger LOG = LoggerFactory.getLogger(RegisterArg.class); private static final Logger LOG = LoggerFactory.getLogger(RegisterArg.class);
protected final int regNum; protected final int regNum;
protected SSAVar sVar; // not null after SSATransform pass
private SSAVar sVar;
public RegisterArg(int rn) { public RegisterArg(int rn) {
this.regNum = rn; this.regNum = rn;
@@ -80,8 +81,10 @@ public class RegisterArg extends InsnArg implements Named {
setName(name); setName(name);
} }
public void forceType(ArgType type) { public RegisterArg duplicate() {
this.type = type; RegisterArg dup = new RegisterArg(getRegNum(), getType());
dup.setSVar(sVar);
return dup;
} }
/** /**
@@ -138,11 +141,7 @@ public class RegisterArg extends InsnArg implements Named {
if (sVar == null) { if (sVar == null) {
return null; return null;
} }
RegisterArg assign = sVar.getAssign(); return sVar.getAssign().getParentInsn();
if (assign != null) {
return assign.getParentInsn();
}
return null;
} }
public InsnNode getPhiAssignInsn() { public InsnNode getPhiAssignInsn() {
@@ -150,12 +149,9 @@ public class RegisterArg extends InsnArg implements Named {
if (usePhi != null) { if (usePhi != null) {
return usePhi; return usePhi;
} }
RegisterArg assign = sVar.getAssign(); InsnNode parent = sVar.getAssign().getParentInsn();
if (assign != null) { if (parent != null && parent.getType() == InsnType.PHI) {
InsnNode parent = assign.getParentInsn(); return parent;
if (parent != null && parent.getType() == InsnType.PHI) {
return parent;
}
} }
return null; return null;
} }
@@ -5,25 +5,74 @@ import jadx.core.dex.instructions.PhiInsn;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class SSAVar { public class SSAVar {
private final int regNum; private final int regNum;
private final int version; private final int version;
private VarName varName; private VarName varName;
private int startUseAddr;
private int endUseAddr;
@NotNull
private RegisterArg assign; private RegisterArg assign;
private final List<RegisterArg> useList = new ArrayList<RegisterArg>(2); private final List<RegisterArg> useList = new ArrayList<RegisterArg>(2);
@Nullable
private PhiInsn usedInPhi; private PhiInsn usedInPhi;
private ArgType type; private ArgType type;
private boolean typeImmutable;
public SSAVar(int regNum, int v, RegisterArg assign) { public SSAVar(int regNum, int v, @NotNull RegisterArg assign) {
this.regNum = regNum; this.regNum = regNum;
this.version = v; this.version = v;
this.assign = assign; this.assign = assign;
if (assign != null) { assign.setSVar(this);
assign.setSVar(this); startUseAddr = -1;
endUseAddr = -1;
}
public int getStartAddr() {
if (startUseAddr == -1) {
calcUsageAddrRange();
}
return startUseAddr;
}
public int getEndAddr() {
if (endUseAddr == -1) {
calcUsageAddrRange();
}
return endUseAddr;
}
private void calcUsageAddrRange() {
int start = Integer.MAX_VALUE;
int end = Integer.MIN_VALUE;
if (assign.getParentInsn() != null) {
int insnAddr = assign.getParentInsn().getOffset();
if (insnAddr >= 0) {
start = Math.min(insnAddr, start);
end = Math.max(insnAddr, end);
}
}
for (RegisterArg arg : useList) {
if (arg.getParentInsn() != null) {
int insnAddr = arg.getParentInsn().getOffset();
if (insnAddr >= 0) {
start = Math.min(insnAddr, start);
end = Math.max(insnAddr, end);
}
}
}
if (start != Integer.MAX_VALUE && end != Integer.MIN_VALUE) {
startUseAddr = start;
endUseAddr = end;
} }
} }
@@ -35,11 +84,12 @@ public class SSAVar {
return version; return version;
} }
@NotNull
public RegisterArg getAssign() { public RegisterArg getAssign() {
return assign; return assign;
} }
public void setAssign(RegisterArg assign) { public void setAssign(@NotNull RegisterArg assign) {
this.assign = assign; this.assign = assign;
} }
@@ -68,10 +118,11 @@ public class SSAVar {
} }
} }
public void setUsedInPhi(PhiInsn usedInPhi) { public void setUsedInPhi(@Nullable PhiInsn usedInPhi) {
this.usedInPhi = usedInPhi; this.usedInPhi = usedInPhi;
} }
@Nullable
public PhiInsn getUsedInPhi() { public PhiInsn getUsedInPhi() {
return usedInPhi; return usedInPhi;
} }
@@ -81,24 +132,34 @@ public class SSAVar {
} }
public int getVariableUseCount() { public int getVariableUseCount() {
if (!isUsedInPhi()) { if (usedInPhi == null) {
return useList.size(); return useList.size();
} }
return useList.size() + usedInPhi.getResult().getSVar().getUseCount(); return useList.size() + usedInPhi.getResult().getSVar().getUseCount();
} }
public ArgType getType() { public void setType(ArgType type) {
return type; ArgType acceptedType;
if (typeImmutable) {
// don't change type, just update types in useList
acceptedType = this.type;
} else {
acceptedType = type;
this.type = acceptedType;
}
assign.type = acceptedType;
for (int i = 0, useListSize = useList.size(); i < useListSize; i++) {
useList.get(i).type = acceptedType;
}
} }
public void setType(ArgType type) { public void setTypeImmutable(ArgType type) {
this.type = type; setType(type);
if (assign != null) { this.typeImmutable = true;
assign.type = type; }
}
for (int i = 0, useListSize = useList.size(); i < useListSize; i++) { public boolean isTypeImmutable() {
useList.get(i).type = type; return typeImmutable;
}
} }
public void setName(String name) { public void setName(String name) {
@@ -1,10 +1,10 @@
package jadx.core.dex.instructions.args; package jadx.core.dex.instructions.args;
public class MthParameterArg extends RegisterArg { public class TypeImmutableArg extends RegisterArg {
private boolean isThis; private boolean isThis;
public MthParameterArg(int rn, ArgType type) { public TypeImmutableArg(int rn, ArgType type) {
super(rn, type); super(rn, type);
} }
@@ -15,6 +15,7 @@ public class MthParameterArg extends RegisterArg {
@Override @Override
public void setType(ArgType type) { public void setType(ArgType type) {
// not allowed
} }
public void markAsThis() { public void markAsThis() {
@@ -39,6 +40,7 @@ public class MthParameterArg extends RegisterArg {
if (isThis) { if (isThis) {
sVar.setName("this"); sVar.setName("this");
} }
sVar.setTypeImmutable(type);
super.setSVar(sVar); super.setSVar(sVar);
} }
@@ -47,13 +49,13 @@ public class MthParameterArg extends RegisterArg {
if (this == obj) { if (this == obj) {
return true; return true;
} }
if (!(obj instanceof MthParameterArg)) { if (!(obj instanceof TypeImmutableArg)) {
return false; return false;
} }
if (!super.equals(obj)) { if (!super.equals(obj)) {
return false; return false;
} }
MthParameterArg that = (MthParameterArg) obj; TypeImmutableArg that = (TypeImmutableArg) obj;
return isThis == that.isThis; return isThis == that.isThis;
} }
@@ -52,6 +52,13 @@ public class ConstructorInsn extends InsnNode {
setSourceLine(invoke.getSourceLine()); setSourceLine(invoke.getSourceLine());
} }
public ConstructorInsn(MethodInfo callMth, CallType callType, RegisterArg instanceArg) {
super(InsnType.CONSTRUCTOR, callMth.getArgsCount());
this.callMth = callMth;
this.callType = callType;
this.instanceArg = instanceArg;
}
public MethodInfo getCallMth() { public MethodInfo getCallMth() {
return callMth; return callMth;
} }
@@ -64,6 +71,14 @@ public class ConstructorInsn extends InsnNode {
return callMth.getDeclClass(); return callMth.getDeclClass();
} }
public CallType getCallType() {
return callType;
}
public boolean isNewInstance() {
return callType == CallType.CONSTRUCTOR;
}
public boolean isSuper() { public boolean isSuper() {
return callType == CallType.SUPER; return callType == CallType.SUPER;
} }
@@ -5,13 +5,19 @@ import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.LiteralArg; import jadx.core.dex.instructions.args.LiteralArg;
import jadx.core.dex.instructions.args.RegisterArg; import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.regions.IfCondition; import jadx.core.dex.regions.conditions.IfCondition;
import jadx.core.utils.InsnUtils; import jadx.core.utils.InsnUtils;
import jadx.core.utils.Utils; import jadx.core.utils.Utils;
public class TernaryInsn extends InsnNode { import java.util.List;
private final IfCondition condition; public final class TernaryInsn extends InsnNode {
private IfCondition condition;
public TernaryInsn(IfCondition condition, RegisterArg result) {
this(condition, result, LiteralArg.TRUE, LiteralArg.FALSE);
}
public TernaryInsn(IfCondition condition, RegisterArg result, InsnArg th, InsnArg els) { public TernaryInsn(IfCondition condition, RegisterArg result, InsnArg th, InsnArg els) {
super(InsnType.TERNARY, 2); super(InsnType.TERNARY, 2);
@@ -33,6 +39,26 @@ public class TernaryInsn extends InsnNode {
return condition; return condition;
} }
public void simplifyCondition() {
condition = IfCondition.simplify(condition);
if (condition.getMode() == IfCondition.Mode.NOT) {
invert();
}
}
private void invert() {
condition = IfCondition.invert(condition);
InsnArg tmp = getArg(0);
setArg(0, getArg(1));
setArg(1, tmp);
}
@Override
public void getRegisterArgs(List<RegisterArg> list) {
super.getRegisterArgs(list);
list.addAll(condition.getRegisterArgs());
}
@Override @Override
public boolean equals(Object obj) { public boolean equals(Object obj) {
if (this == obj) { if (this == obj) {
@@ -132,7 +132,7 @@ public class ClassNode extends LineAttrNode implements ILoadable {
try { try {
new AnnotationsParser(this).parse(offset); new AnnotationsParser(this).parse(offset);
} catch (DecodeException e) { } catch (DecodeException e) {
LOG.error("Error parsing annotations in " + this, e); LOG.error("Error parsing annotations in {}", this, e);
} }
} }
} }
@@ -184,7 +184,7 @@ public class ClassNode extends LineAttrNode implements ILoadable {
} }
} }
} catch (JadxRuntimeException e) { } catch (JadxRuntimeException e) {
LOG.error("Class signature parse error: " + this, e); LOG.error("Class signature parse error: {}", this, e);
} }
} }
@@ -198,7 +198,7 @@ public class ClassNode extends LineAttrNode implements ILoadable {
field.setType(gType); field.setType(gType);
} }
} catch (JadxRuntimeException e) { } catch (JadxRuntimeException e) {
LOG.error("Field signature parse error: " + field, e); LOG.error("Field signature parse error: {}", field, e);
} }
} }
} }
@@ -354,6 +354,11 @@ public class ClassNode extends LineAttrNode implements ILoadable {
return parentClass; return parentClass;
} }
public ClassNode getTopParentClass() {
ClassNode parent = getParentClass();
return parent == this ? this : parent.getParentClass();
}
public List<ClassNode> getInnerClasses() { public List<ClassNode> getInnerClasses() {
return innerClasses; return innerClasses;
} }
@@ -377,10 +382,7 @@ public class ClassNode extends LineAttrNode implements ILoadable {
public MethodNode getDefaultConstructor() { public MethodNode getDefaultConstructor() {
for (MethodNode mth : methods) { for (MethodNode mth : methods) {
if (mth.getAccessFlags().isConstructor() if (mth.isDefaultConstructor()) {
&& mth.getMethodInfo().isConstructor()
&& (mth.getMethodInfo().getArgsCount() == 0
|| (mth.getArguments(false) != null && mth.getArguments(false).isEmpty()))) {
return mth; return mth;
} }
} }
@@ -13,6 +13,8 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.jetbrains.annotations.Nullable;
import com.android.dex.ClassData; import com.android.dex.ClassData;
import com.android.dex.ClassData.Method; import com.android.dex.ClassData.Method;
import com.android.dex.ClassDef; import com.android.dex.ClassDef;
@@ -31,16 +33,12 @@ public class DexNode {
private final RootNode root; private final RootNode root;
private final Dex dexBuf; private final Dex dexBuf;
private final List<ClassNode> classes = new ArrayList<ClassNode>(); private final List<ClassNode> classes = new ArrayList<ClassNode>();
private final String[] strings;
private final Map<Object, FieldNode> constFields = new HashMap<Object, FieldNode>(); private final Map<Object, FieldNode> constFields = new HashMap<Object, FieldNode>();
public DexNode(RootNode root, InputFile input) { public DexNode(RootNode root, InputFile input) {
this.root = root; this.root = root;
this.dexBuf = input.getDexBuffer(); this.dexBuf = input.getDexBuffer();
List<String> stringList = dexBuf.strings();
this.strings = stringList.toArray(new String[stringList.size()]);
} }
public void loadClasses() throws DecodeException { public void loadClasses() throws DecodeException {
@@ -53,10 +51,12 @@ public class DexNode {
return classes; return classes;
} }
@Nullable
public ClassNode resolveClass(ClassInfo clsInfo) { public ClassNode resolveClass(ClassInfo clsInfo) {
return root.resolveClass(clsInfo); return root.resolveClass(clsInfo);
} }
@Nullable
public MethodNode resolveMethod(MethodInfo mth) { public MethodNode resolveMethod(MethodInfo mth) {
ClassNode cls = resolveClass(mth.getDeclClass()); ClassNode cls = resolveClass(mth.getDeclClass());
if (cls != null) { if (cls != null) {
@@ -65,6 +65,7 @@ public class DexNode {
return null; return null;
} }
@Nullable
public FieldNode resolveField(FieldInfo field) { public FieldNode resolveField(FieldInfo field) {
ClassNode cls = resolveClass(field.getDeclClass()); ClassNode cls = resolveClass(field.getDeclClass());
if (cls != null) { if (cls != null) {
@@ -80,7 +81,7 @@ public class DexNode {
// DexBuffer wrappers // DexBuffer wrappers
public String getString(int index) { public String getString(int index) {
return strings[index]; return dexBuf.strings().get(index);
} }
public ArgType getType(int index) { public ArgType getType(int index) {
@@ -4,6 +4,8 @@ import jadx.core.dex.attributes.IAttributeNode;
public interface IContainer extends IAttributeNode { public interface IContainer extends IAttributeNode {
// unique id for use in 'toString()' method /**
* Unique id for use in 'toString()' method
*/
String baseString(); String baseString();
} }
@@ -35,6 +35,12 @@ public class InsnNode extends LineAttrNode {
} }
} }
public static InsnNode wrapArg(InsnArg arg) {
InsnNode insn = new InsnNode(InsnType.ONE_ARG, 1);
insn.addArg(arg);
return insn;
}
public void setResult(RegisterArg res) { public void setResult(RegisterArg res) {
if (res != null) { if (res != null) {
res.setParentInsn(this); res.setParentInsn(this);
@@ -101,6 +107,17 @@ public class InsnNode extends LineAttrNode {
return false; return false;
} }
protected boolean removeArg(InsnArg arg) {
int count = getArgsCount();
for (int i = 0; i < count; i++) {
if (arg == arguments.get(i)) {
arguments.remove(i);
return true;
}
}
return false;
}
protected void addReg(DecodedInstruction insn, int i, ArgType type) { protected void addReg(DecodedInstruction insn, int i, ArgType type) {
addArg(InsnArg.reg(insn, i, type)); addArg(InsnArg.reg(insn, i, type));
} }
@@ -135,6 +152,18 @@ public class InsnNode extends LineAttrNode {
} }
} }
public boolean isConstInsn() {
switch (getType()) {
case CONST:
case CONST_STR:
case CONST_CLASS:
return true;
default:
return false;
}
}
public boolean canReorder() { public boolean canReorder() {
switch (getType()) { switch (getType()) {
case CONST: case CONST:
@@ -153,7 +182,6 @@ public class InsnNode extends LineAttrNode {
case NEW_ARRAY: case NEW_ARRAY:
case NEW_MULTIDIM_ARRAY: case NEW_MULTIDIM_ARRAY:
case STR_CONCAT: case STR_CONCAT:
case MOVE_EXCEPTION:
return true; return true;
default: default:
@@ -15,9 +15,9 @@ import jadx.core.dex.instructions.InsnDecoder;
import jadx.core.dex.instructions.SwitchNode; import jadx.core.dex.instructions.SwitchNode;
import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg; import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.MthParameterArg;
import jadx.core.dex.instructions.args.RegisterArg; import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.args.SSAVar; import jadx.core.dex.instructions.args.SSAVar;
import jadx.core.dex.instructions.args.TypeImmutableArg;
import jadx.core.dex.nodes.parser.SignatureParser; import jadx.core.dex.nodes.parser.SignatureParser;
import jadx.core.dex.regions.Region; import jadx.core.dex.regions.Region;
import jadx.core.dex.trycatch.ExcHandlerAttr; import jadx.core.dex.trycatch.ExcHandlerAttr;
@@ -34,6 +34,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -52,6 +53,7 @@ public class MethodNode extends LineAttrNode implements ILoadable {
private final Method methodData; private final Method methodData;
private int regsCount; private int regsCount;
private InsnNode[] instructions; private InsnNode[] instructions;
private int codeSize;
private int debugInfoOffset; private int debugInfoOffset;
private boolean noCode; private boolean noCode;
@@ -82,6 +84,7 @@ public class MethodNode extends LineAttrNode implements ILoadable {
try { try {
if (noCode) { if (noCode) {
regsCount = 0; regsCount = 0;
codeSize = 0;
initMethodTypes(); initMethodTypes();
return; return;
} }
@@ -94,6 +97,7 @@ public class MethodNode extends LineAttrNode implements ILoadable {
InsnDecoder decoder = new InsnDecoder(this); InsnDecoder decoder = new InsnDecoder(this);
decoder.decodeInsns(mthCode); decoder.decodeInsns(mthCode);
instructions = decoder.process(); instructions = decoder.process();
codeSize = instructions.length;
initTryCatches(mthCode); initTryCatches(mthCode);
initJumps(); initJumps();
@@ -144,8 +148,7 @@ public class MethodNode extends LineAttrNode implements ILoadable {
return false; return false;
} }
if (!mthInfo.isConstructor()) { if (!mthInfo.isConstructor()) {
LOG.warn("Wrong signature parse result: " + sp + " -> " + argsTypes LOG.warn("Wrong signature parse result: {} -> {}, not generic version: {}", sp, argsTypes, mthArgs);
+ ", not generic version: " + mthArgs);
return false; return false;
} else if (getParentClass().getAccessFlags().isEnum()) { } else if (getParentClass().getAccessFlags().isEnum()) {
// TODO: // TODO:
@@ -161,7 +164,7 @@ public class MethodNode extends LineAttrNode implements ILoadable {
} }
initArguments(argsTypes); initArguments(argsTypes);
} catch (JadxRuntimeException e) { } catch (JadxRuntimeException e) {
LOG.error("Method signature parse error: " + this, e); LOG.error("Method signature parse error: {}", this, e);
return false; return false;
} }
return true; return true;
@@ -180,7 +183,7 @@ public class MethodNode extends LineAttrNode implements ILoadable {
if (accFlags.isStatic()) { if (accFlags.isStatic()) {
thisArg = null; thisArg = null;
} else { } else {
MthParameterArg arg = InsnArg.parameterReg(pos - 1, parentClass.getClassInfo().getType()); TypeImmutableArg arg = InsnArg.typeImmutableReg(pos - 1, parentClass.getClassInfo().getType());
arg.markAsThis(); arg.markAsThis();
thisArg = arg; thisArg = arg;
} }
@@ -190,7 +193,7 @@ public class MethodNode extends LineAttrNode implements ILoadable {
} }
argsList = new ArrayList<RegisterArg>(args.size()); argsList = new ArrayList<RegisterArg>(args.size());
for (ArgType arg : args) { for (ArgType arg : args) {
argsList.add(InsnArg.parameterReg(pos, arg)); argsList.add(InsnArg.typeImmutableReg(pos, arg));
pos += arg.getRegCount(); pos += arg.getRegCount();
} }
} }
@@ -201,9 +204,8 @@ public class MethodNode extends LineAttrNode implements ILoadable {
list.add(thisArg); list.add(thisArg);
list.addAll(argsList); list.addAll(argsList);
return list; return list;
} else {
return argsList;
} }
return argsList;
} }
public RegisterArg removeFirstArgument() { public RegisterArg removeFirstArgument() {
@@ -227,6 +229,9 @@ public class MethodNode extends LineAttrNode implements ILoadable {
InsnNode[] insnByOffset = instructions; InsnNode[] insnByOffset = instructions;
CatchHandler[] catchBlocks = mthCode.getCatchHandlers(); CatchHandler[] catchBlocks = mthCode.getCatchHandlers();
Try[] tries = mthCode.getTries(); Try[] tries = mthCode.getTries();
if (catchBlocks.length == 0 && tries.length == 0) {
return;
}
int hc = 0; int hc = 0;
Set<Integer> addrs = new HashSet<Integer>(); Set<Integer> addrs = new HashSet<Integer>();
@@ -271,7 +276,6 @@ public class MethodNode extends LineAttrNode implements ILoadable {
for (TryCatchBlock ct : catches) { for (TryCatchBlock ct : catches) {
for (ExceptionHandler eh : ct.getHandlers()) { for (ExceptionHandler eh : ct.getHandlers()) {
int addr = eh.getHandleOffset(); int addr = eh.getHandleOffset();
// assert addrs.add(addr) : "Instruction already contains EXC_HANDLER attribute";
ExcHandlerAttr ehAttr = new ExcHandlerAttr(ct, eh); ExcHandlerAttr ehAttr = new ExcHandlerAttr(ct, eh);
insnByOffset[addr].addAttr(ehAttr); insnByOffset[addr].addAttr(ehAttr);
} }
@@ -284,13 +288,17 @@ public class MethodNode extends LineAttrNode implements ILoadable {
int offset = aTry.getStartAddress(); int offset = aTry.getStartAddress();
int end = offset + aTry.getInstructionCount() - 1; int end = offset + aTry.getInstructionCount() - 1;
insnByOffset[offset].add(AFlag.TRY_ENTER); InsnNode insn = insnByOffset[offset];
insn.add(AFlag.TRY_ENTER);
while (offset <= end && offset >= 0) { while (offset <= end && offset >= 0) {
catchBlock.addInsn(insnByOffset[offset]); insn = insnByOffset[offset];
catchBlock.addInsn(insn);
offset = InsnDecoder.getNextInsnOffset(insnByOffset, offset); offset = InsnDecoder.getNextInsnOffset(insnByOffset, offset);
} }
if (insnByOffset[end] != null) { if (insnByOffset[end] != null) {
insnByOffset[end].add(AFlag.TRY_LEAVE); insnByOffset[end].add(AFlag.TRY_LEAVE);
} else {
insn.add(AFlag.TRY_LEAVE);
} }
} }
} }
@@ -303,18 +311,17 @@ public class MethodNode extends LineAttrNode implements ILoadable {
continue; continue;
} }
switch (insn.getType()) { switch (insn.getType()) {
case SWITCH: { case SWITCH:
SwitchNode sw = (SwitchNode) insn; SwitchNode sw = (SwitchNode) insn;
for (int target : sw.getTargets()) { for (int target : sw.getTargets()) {
addJump(insnByOffset, offset, target); addJump(insnByOffset, offset, target);
} }
// default case // default case
int next = InsnDecoder.getNextInsnOffset(insnByOffset, offset); int nextInsnOffset = InsnDecoder.getNextInsnOffset(insnByOffset, offset);
if (next != -1) { if (nextInsnOffset != -1) {
addJump(insnByOffset, offset, next); addJump(insnByOffset, offset, nextInsnOffset);
} }
break; break;
}
case IF: case IF:
int next = InsnDecoder.getNextInsnOffset(insnByOffset, offset); int next = InsnDecoder.getNextInsnOffset(insnByOffset, offset);
@@ -339,12 +346,7 @@ public class MethodNode extends LineAttrNode implements ILoadable {
} }
public String getName() { public String getName() {
String name = mthInfo.getName(); return mthInfo.getName();
if (name.equals(parentClass.getShortName())) {
return name + "_";
} else {
return name;
}
} }
public ClassNode getParentClass() { public ClassNode getParentClass() {
@@ -355,6 +357,10 @@ public class MethodNode extends LineAttrNode implements ILoadable {
return noCode; return noCode;
} }
public int getCodeSize() {
return codeSize;
}
public InsnNode[] getInstructions() { public InsnNode[] getInstructions() {
return instructions; return instructions;
} }
@@ -404,10 +410,14 @@ public class MethodNode extends LineAttrNode implements ILoadable {
if (loops.isEmpty()) { if (loops.isEmpty()) {
loops = new ArrayList<LoopInfo>(5); loops = new ArrayList<LoopInfo>(5);
} }
loop.setId(loops.size());
loops.add(loop); loops.add(loop);
} }
public LoopInfo getLoopForBlock(BlockNode block) { public LoopInfo getLoopForBlock(BlockNode block) {
if (loops.isEmpty()) {
return null;
}
for (LoopInfo loop : loops) { for (LoopInfo loop : loops) {
if (loop.getLoopBlocks().contains(block)) { if (loop.getLoopBlocks().contains(block)) {
return loop; return loop;
@@ -416,10 +426,27 @@ public class MethodNode extends LineAttrNode implements ILoadable {
return null; return null;
} }
public List<LoopInfo> getAllLoopsForBlock(BlockNode block) {
if (loops.isEmpty()) {
return Collections.emptyList();
}
List<LoopInfo> list = new ArrayList<LoopInfo>(loops.size());
for (LoopInfo loop : loops) {
if (loop.getLoopBlocks().contains(block)) {
list.add(loop);
}
}
return list;
}
public int getLoopsCount() { public int getLoopsCount() {
return loops.size(); return loops.size();
} }
public Iterable<LoopInfo> getLoops() {
return loops;
}
public ExceptionHandler addExceptionHandler(ExceptionHandler handler) { public ExceptionHandler addExceptionHandler(ExceptionHandler handler) {
if (exceptionHandlers.isEmpty()) { if (exceptionHandlers.isEmpty()) {
exceptionHandlers = new ArrayList<ExceptionHandler>(2); exceptionHandlers = new ArrayList<ExceptionHandler>(2);
@@ -467,6 +494,24 @@ public class MethodNode extends LineAttrNode implements ILoadable {
return false; return false;
} }
public boolean isDefaultConstructor() {
boolean result = false;
if (accFlags.isConstructor() && mthInfo.isConstructor()) {
int defaultArgCount = 0;
/** workaround for non-static inner class constructor, that has synthetic argument */
if (parentClass.getClassInfo().isInner()
&& !parentClass.getAccessFlags().isStatic()) {
ClassNode outerCls = parentClass.getParentClass();
if (argsList != null && !argsList.isEmpty()
&& argsList.get(0).getType().equals(outerCls.getClassInfo().getType())) {
defaultArgCount = 1;
}
}
result = (argsList == null) || (argsList.size() == defaultArgCount);
}
return result;
}
public int getRegsCount() { public int getRegsCount() {
return regsCount; return regsCount;
} }
@@ -475,7 +520,7 @@ public class MethodNode extends LineAttrNode implements ILoadable {
return debugInfoOffset; return debugInfoOffset;
} }
public SSAVar makeNewSVar(int regNum, int[] versions, RegisterArg arg) { public SSAVar makeNewSVar(int regNum, int[] versions, @NotNull RegisterArg arg) {
SSAVar var = new SSAVar(regNum, versions[regNum], arg); SSAVar var = new SSAVar(regNum, versions[regNum], arg);
versions[regNum]++; versions[regNum]++;
if (sVars.isEmpty()) { if (sVars.isEmpty()) {
@@ -3,6 +3,7 @@ package jadx.core.dex.nodes.parser;
import jadx.core.dex.attributes.nodes.SourceFileAttr; import jadx.core.dex.attributes.nodes.SourceFileAttr;
import jadx.core.dex.instructions.args.InsnArg; import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.RegisterArg; import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.args.SSAVar;
import jadx.core.dex.nodes.DexNode; import jadx.core.dex.nodes.DexNode;
import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.MethodNode;
@@ -112,8 +113,9 @@ public class DebugInfoParser {
int regNum = section.readUleb128(); int regNum = section.readUleb128();
LocalVar var = locals[regNum]; LocalVar var = locals[regNum];
if (var != null) { if (var != null) {
var.end(addr, line); if (var.end(addr, line)) {
setVar(var); setVar(var);
}
var.start(addr, line); var.start(addr, line);
} }
break; break;
@@ -160,7 +162,7 @@ public class DebugInfoParser {
for (LocalVar var : locals) { for (LocalVar var : locals) {
if (var != null && !var.isEnd()) { if (var != null && !var.isEnd()) {
var.end(addr, line); var.end(mth.getCodeSize() - 1, line);
setVar(var); setVar(var);
} }
} }
@@ -169,6 +171,8 @@ public class DebugInfoParser {
private int addrChange(int addr, int addrInc, int line) { private int addrChange(int addr, int addrInc, int line) {
int newAddr = addr + addrInc; int newAddr = addr + addrInc;
int maxAddr = insnByOffset.length - 1;
newAddr = Math.min(newAddr, maxAddr);
for (int i = addr + 1; i <= newAddr; i++) { for (int i = addr + 1; i <= newAddr; i++) {
InsnNode insn = insnByOffset[i]; InsnNode insn = insnByOffset[i];
if (insn == null) { if (insn == null) {
@@ -236,7 +240,27 @@ public class DebugInfoParser {
if (arg != null && arg.isRegister()) { if (arg != null && arg.isRegister()) {
RegisterArg reg = (RegisterArg) arg; RegisterArg reg = (RegisterArg) arg;
if (var.getRegNum() == reg.getRegNum()) { if (var.getRegNum() == reg.getRegNum()) {
reg.mergeDebugInfo(var.getType(), var.getName()); SSAVar ssaVar = reg.getSVar();
boolean mergeRequired = false;
if (ssaVar != null) {
int ssaEnd = ssaVar.getEndAddr();
int ssaStart = ssaVar.getStartAddr();
int localStart = var.getStartAddr();
int localEnd = var.getEndAddr();
boolean isIntersected = !((localEnd < ssaStart) || (ssaEnd < localStart));
if (isIntersected && (ssaEnd <= localEnd)) {
mergeRequired = true;
}
} else {
mergeRequired = true;
}
if (mergeRequired) {
reg.mergeDebugInfo(var.getType(), var.getName());
}
} }
} }
} }
@@ -21,9 +21,9 @@ final class LocalVar {
public LocalVar(DexNode dex, int rn, int nameId, int typeId, int signId) { public LocalVar(DexNode dex, int rn, int nameId, int typeId, int signId) {
this.regNum = rn; this.regNum = rn;
String name = (nameId == DexNode.NO_INDEX ? null : dex.getString(nameId)); String name = nameId == DexNode.NO_INDEX ? null : dex.getString(nameId);
ArgType type = (typeId == DexNode.NO_INDEX ? null : dex.getType(typeId)); ArgType type = typeId == DexNode.NO_INDEX ? null : dex.getType(typeId);
String sign = (signId == DexNode.NO_INDEX ? null : dex.getString(signId)); String sign = signId == DexNode.NO_INDEX ? null : dex.getString(signId);
init(name, type, sign); init(name, type, sign);
} }
@@ -41,7 +41,7 @@ final class LocalVar {
type = gType; type = gType;
} }
} catch (Exception e) { } catch (Exception e) {
LOG.error("Can't parse signature for local variable: " + sign, e); LOG.error("Can't parse signature for local variable: {}", sign, e);
} }
} }
this.name = name; this.name = name;
@@ -56,10 +56,8 @@ final class LocalVar {
LOG.warn("Generic type in debug info not equals: {} != {}", type, gType); LOG.warn("Generic type in debug info not equals: {} != {}", type, gType);
} }
apply = true; apply = true;
} else if (el.isGenericType()) {
apply = true;
} else { } else {
apply = false; apply = el.isGenericType();
} }
return apply; return apply;
} }
@@ -69,9 +67,20 @@ final class LocalVar {
this.startAddr = addr; this.startAddr = addr;
} }
public void end(int addr, int line) { /**
this.isEnd = true; * Sets end address of local variable
this.endAddr = addr; *
* @param addr address
* @param line source line
* @return <b>true</b> if local variable was active, else <b>false</b>
*/
public boolean end(int addr, int line) {
if (!isEnd) {
this.isEnd = true;
this.endAddr = addr;
return true;
}
return false;
} }
public int getRegNum() { public int getRegNum() {
@@ -1,37 +0,0 @@
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 baseString() {
return container.baseString();
}
@Override
public String toString() {
return "TERN:" + container;
}
}
@@ -0,0 +1,62 @@
package jadx.core.dex.regions;
import jadx.core.dex.nodes.IContainer;
import jadx.core.dex.nodes.IRegion;
import jadx.core.dex.trycatch.ExceptionHandler;
import jadx.core.dex.trycatch.TryCatchBlock;
import jadx.core.utils.Utils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public final class TryCatchRegion extends AbstractRegion {
private final IContainer tryRegion;
private List<IContainer> catchRegions = Collections.emptyList();
private TryCatchBlock tryCatchBlock;
public TryCatchRegion(IRegion parent, IContainer tryRegion) {
super(parent);
this.tryRegion = tryRegion;
}
public IContainer getTryRegion() {
return tryRegion;
}
public List<IContainer> getCatchRegions() {
return catchRegions;
}
public TryCatchBlock geTryCatchBlock() {
return tryCatchBlock;
}
public void setTryCatchBlock(TryCatchBlock tryCatchBlock) {
this.tryCatchBlock = tryCatchBlock;
this.catchRegions = new ArrayList<IContainer>(tryCatchBlock.getHandlersCount());
for (ExceptionHandler handler : tryCatchBlock.getHandlers()) {
catchRegions.add(handler.getHandlerRegion());
}
}
@Override
public List<IContainer> getSubBlocks() {
List<IContainer> all = new ArrayList<IContainer>(1 + catchRegions.size());
all.add(tryRegion);
all.addAll(catchRegions);
return Collections.unmodifiableList(all);
}
@Override
public String baseString() {
return tryRegion.baseString();
}
@Override
public String toString() {
return "Try: " + tryRegion
+ " catches: " + Utils.listToString(catchRegions);
}
}
@@ -1,4 +1,4 @@
package jadx.core.dex.regions; package jadx.core.dex.regions.conditions;
import jadx.core.dex.instructions.IfNode; import jadx.core.dex.instructions.IfNode;
import jadx.core.dex.instructions.IfOp; import jadx.core.dex.instructions.IfOp;
@@ -1,9 +1,8 @@
package jadx.core.dex.regions; package jadx.core.dex.regions.conditions;
import jadx.core.dex.instructions.IfNode; import jadx.core.dex.instructions.IfNode;
import jadx.core.dex.instructions.IfOp; import jadx.core.dex.instructions.IfOp;
import jadx.core.dex.instructions.InsnType; 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.InsnWrapArg;
import jadx.core.dex.instructions.args.LiteralArg; import jadx.core.dex.instructions.args.LiteralArg;
import jadx.core.dex.instructions.args.RegisterArg; import jadx.core.dex.instructions.args.RegisterArg;
@@ -22,6 +21,7 @@ public final class IfCondition {
public enum Mode { public enum Mode {
COMPARE, COMPARE,
TERNARY,
NOT, NOT,
AND, AND,
OR OR
@@ -64,6 +64,10 @@ public final class IfCondition {
return new IfCondition(new Compare(insn)); return new IfCondition(new Compare(insn));
} }
public static IfCondition ternary(IfCondition a, IfCondition b, IfCondition c) {
return new IfCondition(Mode.TERNARY, Arrays.asList(a, b, c));
}
public static IfCondition merge(Mode mode, IfCondition a, IfCondition b) { public static IfCondition merge(Mode mode, IfCondition a, IfCondition b) {
if (a.getMode() == mode) { if (a.getMode() == mode) {
IfCondition n = new IfCondition(a); IfCondition n = new IfCondition(a);
@@ -89,6 +93,10 @@ public final class IfCondition {
return args.get(1); return args.get(1);
} }
public IfCondition third() {
return args.get(2);
}
public void addArg(IfCondition c) { public void addArg(IfCondition c) {
args.add(c); args.add(c);
} }
@@ -106,6 +114,8 @@ public final class IfCondition {
switch (mode) { switch (mode) {
case COMPARE: case COMPARE:
return new IfCondition(cond.getCompare().invert()); return new IfCondition(cond.getCompare().invert());
case TERNARY:
return ternary(not(cond.first()), cond.third(), cond.second());
case NOT: case NOT:
return cond.first(); return cond.first();
case AND: case AND:
@@ -154,7 +164,10 @@ public final class IfCondition {
cond = new IfCondition(cond.getMode(), args); cond = new IfCondition(cond.getMode(), args);
} }
if (cond.getMode() == Mode.NOT && cond.first().getMode() == Mode.NOT) { if (cond.getMode() == Mode.NOT && cond.first().getMode() == Mode.NOT) {
cond = cond.first().first(); cond = invert(cond.first());
}
if (cond.getMode() == Mode.TERNARY && cond.first().getMode() == Mode.NOT) {
cond = invert(cond);
} }
// for condition with a lot of negations => make invert // for condition with a lot of negations => make invert
@@ -195,14 +208,7 @@ public final class IfCondition {
public List<RegisterArg> getRegisterArgs() { public List<RegisterArg> getRegisterArgs() {
List<RegisterArg> list = new LinkedList<RegisterArg>(); List<RegisterArg> list = new LinkedList<RegisterArg>();
if (mode == Mode.COMPARE) { if (mode == Mode.COMPARE) {
InsnArg a = compare.getA(); compare.getInsn().getRegisterArgs(list);
if (a.isRegister()) {
list.add((RegisterArg) a);
}
InsnArg b = compare.getB();
if (b.isRegister()) {
list.add((RegisterArg) b);
}
} else { } else {
for (IfCondition arg : args) { for (IfCondition arg : args) {
list.addAll(arg.getRegisterArgs()); list.addAll(arg.getRegisterArgs());
@@ -216,6 +222,8 @@ public final class IfCondition {
switch (mode) { switch (mode) {
case COMPARE: case COMPARE:
return compare.toString(); return compare.toString();
case TERNARY:
return first() + " ? " + second() + " : " + third();
case NOT: case NOT:
return "!" + first(); return "!" + first();
case AND: case AND:
@@ -1,4 +1,4 @@
package jadx.core.dex.regions; package jadx.core.dex.regions.conditions;
import jadx.core.dex.nodes.BlockNode; import jadx.core.dex.nodes.BlockNode;
@@ -7,34 +7,51 @@ import java.util.Set;
public final class IfInfo { public final class IfInfo {
private final IfCondition condition; private final IfCondition condition;
private final Set<BlockNode> mergedBlocks = new HashSet<BlockNode>(); private final Set<BlockNode> mergedBlocks;
private final BlockNode thenBlock; private final BlockNode thenBlock;
private final BlockNode elseBlock; private final BlockNode elseBlock;
private final Set<BlockNode> skipBlocks;
private BlockNode outBlock; private BlockNode outBlock;
@Deprecated @Deprecated
private BlockNode ifBlock; private BlockNode ifBlock;
public IfInfo(IfCondition condition, BlockNode thenBlock, BlockNode elseBlock) { public IfInfo(IfCondition condition, BlockNode thenBlock, BlockNode elseBlock) {
this.condition = condition; this(condition, thenBlock, elseBlock, new HashSet<BlockNode>(), new HashSet<BlockNode>());
this.thenBlock = thenBlock;
this.elseBlock = elseBlock;
} }
public IfInfo(IfCondition condition, IfInfo info) { public IfInfo(IfCondition condition, IfInfo info) {
this(condition, info.getThenBlock(), info.getElseBlock(), info.getMergedBlocks(), info.getSkipBlocks());
}
public IfInfo(IfInfo info, BlockNode thenBlock, BlockNode elseBlock) {
this(info.getCondition(), thenBlock, elseBlock, info.getMergedBlocks(), info.getSkipBlocks());
}
private IfInfo(IfCondition condition, BlockNode thenBlock, BlockNode elseBlock,
Set<BlockNode> mergedBlocks, Set<BlockNode> skipBlocks) {
this.condition = condition; this.condition = condition;
this.thenBlock = info.getThenBlock(); this.thenBlock = thenBlock;
this.elseBlock = info.getElseBlock(); this.elseBlock = elseBlock;
this.mergedBlocks.addAll(info.getMergedBlocks()); this.mergedBlocks = mergedBlocks;
this.skipBlocks = skipBlocks;
} }
public static IfInfo invert(IfInfo info) { public static IfInfo invert(IfInfo info) {
IfInfo tmpIf = new IfInfo(IfCondition.invert(info.getCondition()), IfCondition invertedCondition = IfCondition.invert(info.getCondition());
info.getElseBlock(), info.getThenBlock()); IfInfo tmpIf = new IfInfo(invertedCondition,
info.getElseBlock(), info.getThenBlock(),
info.getMergedBlocks(), info.getSkipBlocks());
tmpIf.setIfBlock(info.getIfBlock()); tmpIf.setIfBlock(info.getIfBlock());
tmpIf.getMergedBlocks().addAll(info.getMergedBlocks());
return tmpIf; return tmpIf;
} }
public void merge(IfInfo... arr) {
for (IfInfo info : arr) {
mergedBlocks.addAll(info.getMergedBlocks());
skipBlocks.addAll(info.getSkipBlocks());
}
}
public IfCondition getCondition() { public IfCondition getCondition() {
return condition; return condition;
} }
@@ -43,6 +60,10 @@ public final class IfInfo {
return mergedBlocks; return mergedBlocks;
} }
public Set<BlockNode> getSkipBlocks() {
return skipBlocks;
}
public BlockNode getThenBlock() { public BlockNode getThenBlock() {
return thenBlock; return thenBlock;
} }
@@ -1,8 +1,9 @@
package jadx.core.dex.regions; package jadx.core.dex.regions.conditions;
import jadx.core.dex.nodes.BlockNode; import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.IContainer; import jadx.core.dex.nodes.IContainer;
import jadx.core.dex.nodes.IRegion; import jadx.core.dex.nodes.IRegion;
import jadx.core.dex.regions.AbstractRegion;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
@@ -16,8 +17,6 @@ public final class IfRegion extends AbstractRegion {
private IContainer thenRegion; private IContainer thenRegion;
private IContainer elseRegion; private IContainer elseRegion;
private TernaryRegion ternRegion;
public IfRegion(IRegion parent, BlockNode header) { public IfRegion(IRegion parent, BlockNode header) {
super(parent); super(parent);
assert header.getInstructions().size() == 1; assert header.getInstructions().size() == 1;
@@ -53,14 +52,6 @@ public final class IfRegion extends AbstractRegion {
return header; return header;
} }
public void setTernRegion(TernaryRegion ternRegion) {
this.ternRegion = ternRegion;
}
public TernaryRegion getTernRegion() {
return ternRegion;
}
public boolean simplifyCondition() { public boolean simplifyCondition() {
IfCondition cond = IfCondition.simplify(condition); IfCondition cond = IfCondition.simplify(condition);
if (cond != condition) { if (cond != condition) {
@@ -87,10 +78,7 @@ public final class IfRegion extends AbstractRegion {
@Override @Override
public List<IContainer> getSubBlocks() { public List<IContainer> getSubBlocks() {
if (ternRegion != null) { List<IContainer> all = new ArrayList<IContainer>(3);
return ternRegion.getSubBlocks();
}
ArrayList<IContainer> all = new ArrayList<IContainer>(3);
all.add(header); all.add(header);
if (thenRegion != null) { if (thenRegion != null) {
all.add(thenRegion); all.add(thenRegion);
@@ -116,9 +104,6 @@ public final class IfRegion extends AbstractRegion {
@Override @Override
public String baseString() { public String baseString() {
if (ternRegion != null) {
return ternRegion.baseString();
}
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
if (thenRegion != null) { if (thenRegion != null) {
sb.append(thenRegion.baseString()); sb.append(thenRegion.baseString());
@@ -131,9 +116,6 @@ public final class IfRegion extends AbstractRegion {
@Override @Override
public String toString() { public String toString() {
if (ternRegion != null) {
return ternRegion.toString();
}
return "IF(" + condition + ") then " + thenRegion + " else " + elseRegion; return "IF(" + condition + ") then " + thenRegion + " else " + elseRegion;
} }
} }
@@ -0,0 +1,22 @@
package jadx.core.dex.regions.loops;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.RegisterArg;
public final class ForEachLoop extends LoopType {
private final RegisterArg varArg;
private final InsnArg iterableArg;
public ForEachLoop(RegisterArg varArg, InsnArg iterableArg) {
this.varArg = varArg;
this.iterableArg = iterableArg;
}
public RegisterArg getVarArg() {
return varArg;
}
public InsnArg getIterableArg() {
return iterableArg;
}
}
@@ -0,0 +1,22 @@
package jadx.core.dex.regions.loops;
import jadx.core.dex.nodes.InsnNode;
public final class ForLoop extends LoopType {
private final InsnNode initInsn;
private final InsnNode incrInsn;
public ForLoop(InsnNode initInsn, InsnNode incrInsn) {
this.initInsn = initInsn;
this.incrInsn = incrInsn;
}
public InsnNode getInitInsn() {
return initInsn;
}
public InsnNode getIncrInsn() {
return incrInsn;
}
}
@@ -1,11 +1,14 @@
package jadx.core.dex.regions; package jadx.core.dex.regions.loops;
import jadx.core.dex.attributes.nodes.LoopInfo;
import jadx.core.dex.instructions.IfNode; import jadx.core.dex.instructions.IfNode;
import jadx.core.dex.instructions.args.RegisterArg; import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.BlockNode; import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.IContainer; import jadx.core.dex.nodes.IContainer;
import jadx.core.dex.nodes.IRegion; import jadx.core.dex.nodes.IRegion;
import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.regions.AbstractRegion;
import jadx.core.dex.regions.conditions.IfCondition;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
@@ -13,21 +16,29 @@ import java.util.List;
public final class LoopRegion extends AbstractRegion { public final class LoopRegion extends AbstractRegion {
private final LoopInfo info;
// loop header contains one 'if' insn, equals null for infinite loop // loop header contains one 'if' insn, equals null for infinite loop
private IfCondition condition; private IfCondition condition;
private final BlockNode conditionBlock; private final BlockNode conditionBlock;
// instruction which must be executed before condition in every loop // instruction which must be executed before condition in every loop
private BlockNode preCondition; private BlockNode preCondition;
private IContainer body; private IRegion body;
private final boolean conditionAtEnd; private final boolean conditionAtEnd;
public LoopRegion(IRegion parent, BlockNode header, boolean reversed) { private LoopType type;
public LoopRegion(IRegion parent, LoopInfo info, BlockNode header, boolean reversed) {
super(parent); super(parent);
this.info = info;
this.conditionBlock = header; this.conditionBlock = header;
this.condition = IfCondition.fromIfBlock(header); this.condition = IfCondition.fromIfBlock(header);
this.conditionAtEnd = reversed; this.conditionAtEnd = reversed;
} }
public LoopInfo getInfo() {
return info;
}
public IfCondition getCondition() { public IfCondition getCondition() {
return condition; return condition;
} }
@@ -40,11 +51,11 @@ public final class LoopRegion extends AbstractRegion {
return conditionBlock; return conditionBlock;
} }
public IContainer getBody() { public IRegion getBody() {
return body; return body;
} }
public void setBody(IContainer body) { public void setBody(IRegion body) {
this.body = body; this.body = body;
} }
@@ -77,25 +88,24 @@ public final class LoopRegion extends AbstractRegion {
InsnNode insn = insns.get(i); InsnNode insn = insns.get(i);
if (insn.getResult() == null) { if (insn.getResult() == null) {
return false; return false;
} else { }
RegisterArg res = insn.getResult(); RegisterArg res = insn.getResult();
if (res.getSVar().getUseCount() > 1) { if (res.getSVar().getUseCount() > 1) {
return false; return false;
} }
boolean found = false; boolean found = false;
// search result arg in other insns // search result arg in other insns
for (int j = i + 1; j < size; j++) { 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)) {
found = true; found = true;
} }
if (!found) { }
return false; // or in if insn
} if (!found && ifInsn.containsArg(res)) {
found = true;
}
if (!found) {
return false;
} }
} }
return true; return true;
@@ -116,6 +126,14 @@ public final class LoopRegion extends AbstractRegion {
} }
} }
public LoopType getType() {
return type;
}
public void setType(LoopType type) {
this.type = type;
}
@Override @Override
public List<IContainer> getSubBlocks() { public List<IContainer> getSubBlocks() {
List<IContainer> all = new ArrayList<IContainer>(3); List<IContainer> all = new ArrayList<IContainer>(3);
@@ -131,6 +149,11 @@ public final class LoopRegion extends AbstractRegion {
return Collections.unmodifiableList(all); return Collections.unmodifiableList(all);
} }
@Override
public boolean replaceSubBlock(IContainer oldBlock, IContainer newBlock) {
return false;
}
@Override @Override
public String baseString() { public String baseString() {
return body.baseString(); return body.baseString();
@@ -138,6 +161,6 @@ public final class LoopRegion extends AbstractRegion {
@Override @Override
public String toString() { public String toString() {
return "LOOP: " + baseString(); return "LOOP:" + info.getId() + ": " + baseString();
} }
} }
@@ -0,0 +1,4 @@
package jadx.core.dex.regions.loops;
public abstract class LoopType {
}
@@ -2,7 +2,7 @@ package jadx.core.dex.trycatch;
import jadx.core.Consts; import jadx.core.Consts;
import jadx.core.dex.info.ClassInfo; import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.instructions.args.NamedArg; import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.nodes.BlockNode; import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.IContainer; import jadx.core.dex.nodes.IContainer;
import jadx.core.utils.InsnUtils; import jadx.core.utils.InsnUtils;
@@ -18,7 +18,7 @@ public class ExceptionHandler {
private BlockNode handlerBlock; private BlockNode handlerBlock;
private final List<BlockNode> blocks = new ArrayList<BlockNode>(); private final List<BlockNode> blocks = new ArrayList<BlockNode>();
private IContainer handlerRegion; private IContainer handlerRegion;
private NamedArg arg; private InsnArg arg;
private TryCatchBlock tryBlock; private TryCatchBlock tryBlock;
@@ -63,11 +63,11 @@ public class ExceptionHandler {
this.handlerRegion = handlerRegion; this.handlerRegion = handlerRegion;
} }
public NamedArg getArg() { public InsnArg getArg() {
return arg; return arg;
} }
public void setArg(NamedArg arg) { public void setArg(InsnArg arg) {
this.arg = arg; this.arg = arg;
} }
@@ -16,7 +16,6 @@ import jadx.core.dex.trycatch.CatchAttr;
import jadx.core.dex.trycatch.ExceptionHandler; import jadx.core.dex.trycatch.ExceptionHandler;
import jadx.core.dex.trycatch.SplitterBlockAttr; import jadx.core.dex.trycatch.SplitterBlockAttr;
import jadx.core.utils.BlockUtils; import jadx.core.utils.BlockUtils;
import jadx.core.utils.EmptyBitSet;
import jadx.core.utils.exceptions.JadxRuntimeException; import jadx.core.utils.exceptions.JadxRuntimeException;
import java.util.ArrayList; import java.util.ArrayList;
@@ -28,6 +27,8 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import static jadx.core.utils.EmptyBitSet.EMPTY;
public class BlockMakerVisitor extends AbstractVisitor { public class BlockMakerVisitor extends AbstractVisitor {
// leave these instructions alone in block node // leave these instructions alone in block node
@@ -36,9 +37,8 @@ public class BlockMakerVisitor extends AbstractVisitor {
InsnType.IF, InsnType.IF,
InsnType.SWITCH, InsnType.SWITCH,
InsnType.MONITOR_ENTER, InsnType.MONITOR_ENTER,
InsnType.MONITOR_EXIT); InsnType.MONITOR_EXIT
);
private static final BitSet EMPTY_BITSET = new EmptyBitSet();
@Override @Override
public void visit(MethodNode mth) { public void visit(MethodNode mth) {
@@ -103,7 +103,9 @@ public class BlockMakerVisitor extends AbstractVisitor {
// add this insn in new block // add this insn in new block
block = startNewBlock(mth, -1); block = startNewBlock(mth, -1);
curBlock.add(AFlag.SYNTHETIC); curBlock.add(AFlag.SYNTHETIC);
block.addAttr(new SplitterBlockAttr(curBlock)); SplitterBlockAttr splitter = new SplitterBlockAttr(curBlock);
block.addAttr(splitter);
curBlock.addAttr(splitter);
connect(curBlock, block); connect(curBlock, block);
curBlock = block; curBlock = block;
} else { } else {
@@ -131,12 +133,16 @@ public class BlockMakerVisitor extends AbstractVisitor {
// get synthetic block for handlers // get synthetic block for handlers
SplitterBlockAttr spl = block.get(AType.SPLITTER_BLOCK); SplitterBlockAttr spl = block.get(AType.SPLITTER_BLOCK);
if (catches != null && spl != null) { if (catches != null && spl != null) {
BlockNode connBlock = spl.getBlock(); BlockNode splitterBlock = spl.getBlock();
boolean tryEnd = insn.contains(AFlag.TRY_LEAVE);
for (ExceptionHandler h : catches.getTryBlock().getHandlers()) { for (ExceptionHandler h : catches.getTryBlock().getHandlers()) {
BlockNode destBlock = getBlock(h.getHandleOffset(), blocksMap); BlockNode handlerBlock = getBlock(h.getHandleOffset(), blocksMap);
// skip self loop in handler // skip self loop in handler
if (connBlock != destBlock) { if (splitterBlock != handlerBlock) {
connect(connBlock, destBlock); connect(splitterBlock, handlerBlock);
}
if (tryEnd) {
connect(block, handlerBlock);
} }
} }
} }
@@ -190,6 +196,7 @@ public class BlockMakerVisitor extends AbstractVisitor {
} }
computeDominanceFrontier(mth); computeDominanceFrontier(mth);
registerLoops(mth); registerLoops(mth);
processNestedLoops(mth);
} }
private static BlockNode getBlock(int offset, Map<Integer, BlockNode> blocksMap) { private static BlockNode getBlock(int offset, Map<Integer, BlockNode> blocksMap) {
@@ -291,7 +298,7 @@ public class BlockMakerVisitor extends AbstractVisitor {
private static void computeDominanceFrontier(MethodNode mth) { private static void computeDominanceFrontier(MethodNode mth) {
for (BlockNode exit : mth.getExitBlocks()) { for (BlockNode exit : mth.getExitBlocks()) {
exit.setDomFrontier(EMPTY_BITSET); exit.setDomFrontier(EMPTY);
} }
for (BlockNode block : mth.getBasicBlocks()) { for (BlockNode block : mth.getBasicBlocks()) {
computeBlockDF(mth, block); computeBlockDF(mth, block);
@@ -323,7 +330,7 @@ public class BlockMakerVisitor extends AbstractVisitor {
} }
} }
if (domFrontier == null || domFrontier.cardinality() == 0) { if (domFrontier == null || domFrontier.cardinality() == 0) {
domFrontier = EMPTY_BITSET; domFrontier = EMPTY;
} }
block.setDomFrontier(domFrontier); block.setDomFrontier(domFrontier);
} }
@@ -357,15 +364,40 @@ public class BlockMakerVisitor extends AbstractVisitor {
private static void registerLoops(MethodNode mth) { private static void registerLoops(MethodNode mth) {
for (BlockNode block : mth.getBasicBlocks()) { for (BlockNode block : mth.getBasicBlocks()) {
List<LoopInfo> loops = block.getAll(AType.LOOP);
if (block.contains(AFlag.LOOP_START)) { if (block.contains(AFlag.LOOP_START)) {
for (LoopInfo loop : loops) { for (LoopInfo loop : block.getAll(AType.LOOP)) {
mth.registerLoop(loop); mth.registerLoop(loop);
} }
} }
} }
} }
private static void processNestedLoops(MethodNode mth) {
if (mth.getLoopsCount() == 0) {
return;
}
for (LoopInfo outLoop : mth.getLoops()) {
for (LoopInfo innerLoop : mth.getLoops()) {
if (outLoop == innerLoop) {
continue;
}
if (outLoop.getLoopBlocks().containsAll(innerLoop.getLoopBlocks())) {
LoopInfo parentLoop = innerLoop.getParentLoop();
if (parentLoop != null) {
if (parentLoop.getLoopBlocks().containsAll(outLoop.getLoopBlocks())) {
outLoop.setParentLoop(parentLoop);
innerLoop.setParentLoop(outLoop);
} else {
parentLoop.setParentLoop(outLoop);
}
} else {
innerLoop.setParentLoop(outLoop);
}
}
}
}
}
private static boolean modifyBlocksTree(MethodNode mth) { private static boolean modifyBlocksTree(MethodNode mth) {
for (BlockNode block : mth.getBasicBlocks()) { for (BlockNode block : mth.getBasicBlocks()) {
if (block.getPredecessors().isEmpty() && block != mth.getEnterBlock()) { if (block.getPredecessors().isEmpty() && block != mth.getEnterBlock()) {
@@ -395,9 +427,9 @@ public class BlockMakerVisitor extends AbstractVisitor {
return true; return true;
} }
} }
// insert additional blocks if loop has several exits
if (loops.size() == 1) { if (loops.size() == 1) {
LoopInfo loop = loops.get(0); LoopInfo loop = loops.get(0);
// insert additional blocks for possible 'break' insertion
List<Edge> edges = loop.getExitEdges(); List<Edge> edges = loop.getExitEdges();
if (!edges.isEmpty()) { if (!edges.isEmpty()) {
boolean change = false; boolean change = false;
@@ -412,6 +444,21 @@ public class BlockMakerVisitor extends AbstractVisitor {
return true; return true;
} }
} }
// insert additional blocks for possible 'continue' insertion
BlockNode loopEnd = loop.getEnd();
if (loopEnd.getPredecessors().size() > 1) {
boolean change = false;
List<BlockNode> nodes = new ArrayList<BlockNode>(loopEnd.getPredecessors());
for (BlockNode pred : nodes) {
if (!pred.contains(AFlag.SYNTHETIC)) {
insertBlockBetween(mth, pred, loopEnd);
change = true;
}
}
if (change) {
return true;
}
}
} }
} }
return splitReturn(mth); return splitReturn(mth);
@@ -1,10 +1,11 @@
package jadx.core.dex.visitors; package jadx.core.dex.visitors;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.AType;
import jadx.core.dex.instructions.IfNode; import jadx.core.dex.instructions.IfNode;
import jadx.core.dex.instructions.InsnType; import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.NamedArg; import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.RegisterArg; import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.BlockNode; import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.InsnNode;
@@ -43,30 +44,25 @@ public class BlockProcessingHelper {
* Set exception handler attribute for whole block * Set exception handler attribute for whole block
*/ */
private static void markExceptionHandlers(BlockNode block) { private static void markExceptionHandlers(BlockNode block) {
if (!block.getInstructions().isEmpty()) { if (block.getInstructions().isEmpty()) {
InsnNode me = block.getInstructions().get(0); return;
ExcHandlerAttr handlerAttr = me.get(AType.EXC_HANDLER);
if (handlerAttr != null && me.getType() == InsnType.MOVE_EXCEPTION) {
ExceptionHandler excHandler = handlerAttr.getHandler();
assert me.getOffset() == excHandler.getHandleOffset();
// set correct type for 'move-exception' operation
RegisterArg resArg = me.getResult();
NamedArg excArg = (NamedArg) me.getArg(0);
ArgType type;
if (excHandler.isCatchAll()) {
type = ArgType.THROWABLE;
excArg.setName("th");
} else {
type = excHandler.getCatchType().getType();
excArg.setName("e");
}
resArg.forceType(type);
excArg.setType(type);
excHandler.setArg(excArg);
block.addAttr(handlerAttr);
}
} }
InsnNode me = block.getInstructions().get(0);
ExcHandlerAttr handlerAttr = me.get(AType.EXC_HANDLER);
if (handlerAttr == null || me.getType() != InsnType.MOVE_EXCEPTION) {
return;
}
ExceptionHandler excHandler = handlerAttr.getHandler();
block.addAttr(handlerAttr);
// set correct type for 'move-exception' operation
ArgType type = excHandler.isCatchAll() ? ArgType.THROWABLE : excHandler.getCatchType().getType();
RegisterArg resArg = me.getResult();
resArg = InsnArg.reg(resArg.getRegNum(), type);
me.setResult(resArg);
me.add(AFlag.DONT_INLINE);
excHandler.setArg(resArg);
} }
private static void processExceptionHandlers(MethodNode mth, BlockNode block) { private static void processExceptionHandlers(MethodNode mth, BlockNode block) {
@@ -125,7 +125,7 @@ public class ClassModifier extends AbstractVisitor {
List<InsnNode> insns = mth.getBasicBlocks().get(0).getInstructions(); List<InsnNode> insns = mth.getBasicBlocks().get(0).getInstructions();
if (insns.size() == 1 && insns.get(0).getType() == InsnType.CONSTRUCTOR) { if (insns.size() == 1 && insns.get(0).getType() == InsnType.CONSTRUCTOR) {
ConstructorInsn constr = (ConstructorInsn) insns.get(0); ConstructorInsn constr = (ConstructorInsn) insns.get(0);
if (constr.isThis() && mth.getArguments(false).size() >= 1) { if (constr.isThis() && !mth.getArguments(false).isEmpty()) {
mth.removeFirstArgument(); mth.removeFirstArgument();
mth.add(AFlag.DONT_GENERATE); mth.add(AFlag.DONT_GENERATE);
} }
@@ -167,7 +167,7 @@ public class ClassModifier extends AbstractVisitor {
private static boolean allBlocksEmpty(List<BlockNode> blocks) { private static boolean allBlocksEmpty(List<BlockNode> blocks) {
for (BlockNode block : blocks) { for (BlockNode block : blocks) {
if (block.getInstructions().size() != 0) { if (!block.getInstructions().isEmpty()) {
return false; return false;
} }
} }
@@ -12,6 +12,7 @@ import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.BlockUtils; import jadx.core.utils.BlockUtils;
import jadx.core.utils.EmptyBitSet;
import jadx.core.utils.InsnList; import jadx.core.utils.InsnList;
import jadx.core.utils.exceptions.JadxRuntimeException; import jadx.core.utils.exceptions.JadxRuntimeException;
@@ -55,7 +56,7 @@ public class CodeShrinker extends AbstractVisitor {
} }
public static List<RegisterArg> getArgs(InsnNode insn) { public static List<RegisterArg> getArgs(InsnNode insn) {
LinkedList<RegisterArg> args = new LinkedList<RegisterArg>(); List<RegisterArg> args = new LinkedList<RegisterArg>();
addArgs(insn, args); addArgs(insn, args);
return args; return args;
} }
@@ -96,7 +97,8 @@ public class CodeShrinker extends AbstractVisitor {
} }
private boolean canMove(int from, int to) { private boolean canMove(int from, int to) {
List<RegisterArg> movedArgs = argsList.get(from).getArgs(); ArgsInfo startInfo = argsList.get(from);
List<RegisterArg> movedArgs = startInfo.getArgs();
int start = from + 1; int start = from + 1;
if (start == to) { if (start == to) {
// previous instruction or on edge of inline border // previous instruction or on edge of inline border
@@ -105,9 +107,17 @@ public class CodeShrinker extends AbstractVisitor {
if (start > to) { if (start > to) {
throw new JadxRuntimeException("Invalid inline insn positions: " + start + " - " + to); throw new JadxRuntimeException("Invalid inline insn positions: " + start + " - " + to);
} }
BitSet args = new BitSet(); BitSet movedSet;
for (RegisterArg arg : movedArgs) { if (movedArgs.isEmpty()) {
args.set(arg.getRegNum()); if (startInfo.insn.isConstInsn()) {
return true;
}
movedSet = EmptyBitSet.EMPTY;
} else {
movedSet = new BitSet();
for (RegisterArg arg : movedArgs) {
movedSet.set(arg.getRegNum());
}
} }
for (int i = start; i < to; i++) { for (int i = start; i < to; i++) {
ArgsInfo argsInfo = argsList.get(i); ArgsInfo argsInfo = argsList.get(i);
@@ -115,7 +125,7 @@ public class CodeShrinker extends AbstractVisitor {
continue; continue;
} }
InsnNode curInsn = argsInfo.insn; InsnNode curInsn = argsInfo.insn;
if (!curInsn.canReorder() || usedArgAssign(curInsn, args)) { if (!curInsn.canReorder() || usedArgAssign(curInsn, movedSet)) {
return false; return false;
} }
} }
@@ -187,21 +197,22 @@ public class CodeShrinker extends AbstractVisitor {
List<WrapInfo> wrapList = new ArrayList<WrapInfo>(); List<WrapInfo> wrapList = new ArrayList<WrapInfo>();
for (ArgsInfo argsInfo : argsList) { for (ArgsInfo argsInfo : argsList) {
List<RegisterArg> args = argsInfo.getArgs(); List<RegisterArg> args = argsInfo.getArgs();
for (ListIterator<RegisterArg> it = args.listIterator(args.size()); it.hasPrevious(); ) { if (args.isEmpty()) {
continue;
}
ListIterator<RegisterArg> it = args.listIterator(args.size());
while (it.hasPrevious()) {
RegisterArg arg = it.previous(); RegisterArg arg = it.previous();
// if (arg.getName() != null) { // if (arg.getName() != null) {
// continue; // continue;
// } // }
SSAVar sVar = arg.getSVar(); SSAVar sVar = arg.getSVar();
if (sVar.getAssign() == null) {
continue;
}
// allow inline only one use arg or 'this' // allow inline only one use arg or 'this'
if (sVar.getVariableUseCount() != 1 && !arg.isThis()) { if (sVar.getVariableUseCount() != 1 && !arg.isThis()) {
continue; continue;
} }
InsnNode assignInsn = sVar.getAssign().getParentInsn(); InsnNode assignInsn = sVar.getAssign().getParentInsn();
if (assignInsn == null) { if (assignInsn == null || assignInsn.contains(AFlag.DONT_INLINE)) {
continue; continue;
} }
int assignPos = insnList.getIndex(assignInsn); int assignPos = insnList.getIndex(assignInsn);
@@ -214,6 +225,7 @@ public class CodeShrinker extends AbstractVisitor {
// another block // another block
BlockNode assignBlock = BlockUtils.getBlockByInsn(mth, assignInsn); BlockNode assignBlock = BlockUtils.getBlockByInsn(mth, assignInsn);
if (assignBlock != null if (assignBlock != null
&& assignInsn != arg.getParentInsn()
&& canMoveBetweenBlocks(assignInsn, assignBlock, block, argsInfo.getInsn())) { && canMoveBetweenBlocks(assignInsn, assignBlock, block, argsInfo.getInsn())) {
arg.wrapInstruction(assignInsn); arg.wrapInstruction(assignInsn);
InsnList.remove(assignBlock, assignInsn); InsnList.remove(assignBlock, assignInsn);
@@ -232,7 +244,7 @@ public class CodeShrinker extends AbstractVisitor {
} }
private static boolean canMoveBetweenBlocks(InsnNode assignInsn, BlockNode assignBlock, private static boolean canMoveBetweenBlocks(InsnNode assignInsn, BlockNode assignBlock,
BlockNode useBlock, InsnNode useInsn) { BlockNode useBlock, InsnNode useInsn) {
if (!BlockUtils.isPathExists(assignBlock, useBlock)) { if (!BlockUtils.isPathExists(assignBlock, useBlock)) {
return false; return false;
} }
@@ -292,7 +304,7 @@ public class CodeShrinker extends AbstractVisitor {
} }
} }
// remove method args // remove method args
if (list.size() != 0 && args.size() != 0) { if (!list.isEmpty() && !args.isEmpty()) {
list.removeAll(args); list.removeAll(args);
} }
i++; i++;
@@ -1,9 +1,11 @@
package jadx.core.dex.visitors; package jadx.core.dex.visitors;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.info.FieldInfo; import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.instructions.IndexInsnNode; import jadx.core.dex.instructions.IndexInsnNode;
import jadx.core.dex.instructions.InsnType; import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.InvokeNode; import jadx.core.dex.instructions.InvokeNode;
import jadx.core.dex.instructions.InvokeType;
import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg; import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.LiteralArg; import jadx.core.dex.instructions.args.LiteralArg;
@@ -12,7 +14,6 @@ import jadx.core.dex.instructions.args.SSAVar;
import jadx.core.dex.nodes.BlockNode; import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.BlockUtils;
import jadx.core.utils.InstructionRemover; import jadx.core.utils.InstructionRemover;
import jadx.core.utils.exceptions.JadxException; import jadx.core.utils.exceptions.JadxException;
@@ -30,7 +31,7 @@ public class ConstInlinerVisitor extends AbstractVisitor {
for (BlockNode block : mth.getBasicBlocks()) { for (BlockNode block : mth.getBasicBlocks()) {
toRemove.clear(); toRemove.clear();
for (InsnNode insn : block.getInstructions()) { for (InsnNode insn : block.getInstructions()) {
if (checkInsn(mth, block, insn)) { if (checkInsn(mth, insn)) {
toRemove.add(insn); toRemove.add(insn);
} }
} }
@@ -40,7 +41,7 @@ public class ConstInlinerVisitor extends AbstractVisitor {
} }
} }
private static boolean checkInsn(MethodNode mth, BlockNode block, InsnNode insn) { private static boolean checkInsn(MethodNode mth, InsnNode insn) {
if (insn.getType() != InsnType.CONST) { if (insn.getType() != InsnType.CONST) {
return false; return false;
} }
@@ -48,28 +49,52 @@ public class ConstInlinerVisitor extends AbstractVisitor {
if (!arg.isLiteral()) { if (!arg.isLiteral()) {
return false; return false;
} }
long lit = ((LiteralArg) arg).getLiteral();
SSAVar sVar = insn.getResult().getSVar(); SSAVar sVar = insn.getResult().getSVar();
if (mth.getExceptionHandlersCount() != 0) { if (lit == 0) {
for (RegisterArg useArg : sVar.getUseList()) { if (checkObjectInline(sVar)) {
InsnNode parentInsn = useArg.getParentInsn(); if (sVar.getUseCount() == 1) {
if (parentInsn != null) { insn.getResult().getAssignInsn().add(AFlag.DONT_INLINE);
// TODO: speed up expensive operations
BlockNode useBlock = BlockUtils.getBlockByInsn(mth, parentInsn);
if (!BlockUtils.isCleanPathExists(block, useBlock)) {
return false;
}
} }
return false;
} }
} }
ArgType resType = insn.getResult().getType(); ArgType resType = insn.getResult().getType();
// make sure arg has correct type // make sure arg has correct type
if (!arg.getType().isTypeKnown()) { if (!arg.getType().isTypeKnown()) {
arg.merge(resType); arg.merge(resType);
} }
long lit = ((LiteralArg) arg).getLiteral();
return replaceConst(mth, sVar, lit); return replaceConst(mth, sVar, lit);
} }
/**
* Don't inline null object if:
* - used as instance arg in invoke instruction
* - used in 'array.length'
*/
private static boolean checkObjectInline(SSAVar sVar) {
for (RegisterArg useArg : sVar.getUseList()) {
InsnNode insn = useArg.getParentInsn();
if (insn != null) {
InsnType insnType = insn.getType();
if (insnType == InsnType.INVOKE) {
InvokeNode inv = (InvokeNode) insn;
if (inv.getInvokeType() != InvokeType.STATIC
&& inv.getArg(0) == useArg) {
return true;
}
} else if (insnType == InsnType.ARRAY_LENGTH) {
if (insn.getArg(0) == useArg) {
return true;
}
}
}
}
return false;
}
private static boolean replaceConst(MethodNode mth, SSAVar sVar, long literal) { private static boolean replaceConst(MethodNode mth, SSAVar sVar, long literal) {
List<RegisterArg> use = new ArrayList<RegisterArg>(sVar.getUseList()); List<RegisterArg> use = new ArrayList<RegisterArg>(sVar.getUseList());
int replaceCount = 0; int replaceCount = 0;
@@ -85,6 +110,10 @@ public class ConstInlinerVisitor extends AbstractVisitor {
if (use.size() == 1 || arg.isTypeImmutable()) { if (use.size() == 1 || arg.isTypeImmutable()) {
// arg used only in one place // arg used only in one place
litArg = InsnArg.lit(literal, arg.getType()); litArg = InsnArg.lit(literal, arg.getType());
} else if (useInsn.getType() == InsnType.MOVE
&& !useInsn.getResult().getType().isTypeKnown()) {
// save type for 'move' instructions (hard to find type in chains of 'move')
litArg = InsnArg.lit(literal, arg.getType());
} else { } else {
// in most cases type not equal arg.getType() // in most cases type not equal arg.getType()
// just set unknown type and run type fixer // just set unknown type and run type fixer
@@ -150,7 +179,7 @@ public class ConstInlinerVisitor extends AbstractVisitor {
InvokeNode inv = (InvokeNode) insn; InvokeNode inv = (InvokeNode) insn;
List<ArgType> types = inv.getCallMth().getArgumentsTypes(); List<ArgType> types = inv.getCallMth().getArgumentsTypes();
int count = insn.getArgsCount(); int count = insn.getArgsCount();
int k = (types.size() == count ? 0 : -1); int k = types.size() == count ? 0 : -1;
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
InsnArg arg = insn.getArg(i); InsnArg arg = insn.getArg(i);
if (!arg.getType().isTypeKnown()) { if (!arg.getType().isTypeKnown()) {
@@ -30,16 +30,28 @@ public class DotGraphVisitor extends AbstractVisitor {
private final boolean useRegions; private final boolean useRegions;
private final boolean rawInsn; private final boolean rawInsn;
public DotGraphVisitor(File outDir, boolean useRegions, boolean rawInsn) { public static DotGraphVisitor dump(File outDir) {
return new DotGraphVisitor(outDir, false, false);
}
public static DotGraphVisitor dumpRaw(File outDir) {
return new DotGraphVisitor(outDir, false, true);
}
public static DotGraphVisitor dumpRegions(File outDir) {
return new DotGraphVisitor(outDir, true, false);
}
public static DotGraphVisitor dumpRawRegions(File outDir) {
return new DotGraphVisitor(outDir, true, true);
}
private DotGraphVisitor(File outDir, boolean useRegions, boolean rawInsn) {
this.dir = outDir; this.dir = outDir;
this.useRegions = useRegions; this.useRegions = useRegions;
this.rawInsn = rawInsn; this.rawInsn = rawInsn;
} }
public DotGraphVisitor(File outDir, boolean useRegions) {
this(outDir, useRegions, false);
}
@Override @Override
public void visit(MethodNode mth) { public void visit(MethodNode mth) {
if (mth.isNoCode()) { if (mth.isNoCode()) {
@@ -18,6 +18,7 @@ import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode; import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.exceptions.JadxException; import jadx.core.utils.exceptions.JadxException;
import java.util.ArrayList; import java.util.ArrayList;
@@ -38,13 +39,12 @@ public class EnumVisitor extends AbstractVisitor {
// collect enum fields, remove synthetic // collect enum fields, remove synthetic
List<FieldNode> enumFields = new ArrayList<FieldNode>(); List<FieldNode> enumFields = new ArrayList<FieldNode>();
for (Iterator<FieldNode> it = cls.getFields().iterator(); it.hasNext(); ) { for (FieldNode f : cls.getFields()) {
FieldNode f = it.next();
if (f.getAccessFlags().isEnum()) { if (f.getAccessFlags().isEnum()) {
enumFields.add(f); enumFields.add(f);
it.remove(); f.add(AFlag.DONT_GENERATE);
} else if (f.getAccessFlags().isSynthetic()) { } else if (f.getAccessFlags().isSynthetic()) {
it.remove(); f.add(AFlag.DONT_GENERATE);
} }
} }
@@ -80,9 +80,8 @@ public class EnumVisitor extends AbstractVisitor {
cls.addAttr(attr); cls.addAttr(attr);
if (staticMethod == null) { if (staticMethod == null) {
LOG.warn("Enum class init method not found: {}", cls); ErrorsCounter.classError(cls, "Enum class init method not found");
// for this broken enum puts found fields and mark as inconsistent // for this broken enum puts found fields and mark as inconsistent
cls.add(AFlag.INCONSISTENT_CODE);
for (FieldNode field : enumFields) { for (FieldNode field : enumFields) {
attr.getFields().add(new EnumField(field.getName(), 0)); attr.getFields().add(new EnumField(field.getName(), 0));
} }
@@ -115,21 +114,17 @@ public class EnumVisitor extends AbstractVisitor {
for (InsnNode insn : insns) { for (InsnNode insn : insns) {
if (insn.getType() == InsnType.CONSTRUCTOR) { if (insn.getType() == InsnType.CONSTRUCTOR) {
ConstructorInsn co = (ConstructorInsn) insn; ConstructorInsn co = (ConstructorInsn) insn;
if (insn.getArgsCount() < 2) { if (insn.getArgsCount() < 2) {
continue; continue;
} }
ClassInfo clsInfo = co.getClassType(); ClassInfo clsInfo = co.getClassType();
ClassNode constrCls = cls.dex().resolveClass(clsInfo); ClassNode constrCls = cls.dex().resolveClass(clsInfo);
if (constrCls == null) { if (constrCls == null) {
continue; continue;
} }
if (!clsInfo.equals(cls.getClassInfo()) && !constrCls.getAccessFlags().isEnum()) { if (!clsInfo.equals(cls.getClassInfo()) && !constrCls.getAccessFlags().isEnum()) {
continue; continue;
} }
RegisterArg nameArg = (RegisterArg) insn.getArg(0); RegisterArg nameArg = (RegisterArg) insn.getArg(0);
// InsnArg pos = insn.getArg(1); // InsnArg pos = insn.getArg(1);
// TODO add check: pos == j // TODO add check: pos == j
@@ -137,7 +132,6 @@ public class EnumVisitor extends AbstractVisitor {
if (name == null) { if (name == null) {
throw new JadxException("Unknown enum field name: " + cls); throw new JadxException("Unknown enum field name: " + cls);
} }
EnumField field = new EnumField(name, insn.getArgsCount() - 2); EnumField field = new EnumField(name, insn.getArgsCount() - 2);
attr.getFields().add(field); attr.getFields().add(field);
for (int i = 2; i < insn.getArgsCount(); i++) { for (int i = 2; i < insn.getArgsCount(); i++) {
@@ -3,7 +3,6 @@ package jadx.core.dex.visitors;
import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.nodes.MethodInlineAttr; import jadx.core.dex.attributes.nodes.MethodInlineAttr;
import jadx.core.dex.info.AccessInfo; import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.nodes.BlockNode; import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.MethodNode;
@@ -34,10 +33,8 @@ public class MethodInlineVisitor extends AbstractVisitor {
// synthetic field getter // synthetic field getter
BlockNode block = mth.getBasicBlocks().get(1); BlockNode block = mth.getBasicBlocks().get(1);
InsnNode insn = block.getInstructions().get(0); InsnNode insn = block.getInstructions().get(0);
InsnNode inl = new InsnNode(InsnType.ARGS, 1);
// set arg from 'return' instruction // set arg from 'return' instruction
inl.addArg(insn.getArg(0)); addInlineAttr(mth, InsnNode.wrapArg(insn.getArg(0)));
addInlineAttr(mth, inl);
} else { } else {
// synthetic field setter or method invoke // synthetic field setter or method invoke
if (firstBlock.getInstructions().size() == 1) { if (firstBlock.getInstructions().size() == 1) {
@@ -1,5 +1,6 @@
package jadx.core.dex.visitors; package jadx.core.dex.visitors;
import jadx.core.codegen.TypeGen;
import jadx.core.deobf.NameMapper; import jadx.core.deobf.NameMapper;
import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.AType;
import jadx.core.dex.info.MethodInfo; import jadx.core.dex.info.MethodInfo;
@@ -13,7 +14,9 @@ import jadx.core.dex.instructions.SwitchNode;
import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg; import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.LiteralArg; 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.args.RegisterArg;
import jadx.core.dex.instructions.args.SSAVar;
import jadx.core.dex.instructions.mods.ConstructorInsn; import jadx.core.dex.instructions.mods.ConstructorInsn;
import jadx.core.dex.nodes.BlockNode; import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.ClassNode;
@@ -24,6 +27,7 @@ import jadx.core.dex.trycatch.ExcHandlerAttr;
import jadx.core.dex.trycatch.ExceptionHandler; import jadx.core.dex.trycatch.ExceptionHandler;
import jadx.core.utils.InstructionRemover; import jadx.core.utils.InstructionRemover;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.slf4j.Logger; import org.slf4j.Logger;
@@ -41,8 +45,10 @@ public class ModVisitor extends AbstractVisitor {
if (mth.isNoCode()) { if (mth.isNoCode()) {
return; return;
} }
removeStep(mth);
replaceStep(mth); InstructionRemover remover = new InstructionRemover(mth);
replaceStep(mth, remover);
removeStep(mth, remover);
checkArgsNames(mth); checkArgsNames(mth);
@@ -51,49 +57,16 @@ public class ModVisitor extends AbstractVisitor {
} }
} }
private static void replaceStep(MethodNode mth) { private static void replaceStep(MethodNode mth, InstructionRemover remover) {
ClassNode parentClass = mth.getParentClass(); ClassNode parentClass = mth.getParentClass();
for (BlockNode block : mth.getBasicBlocks()) { for (BlockNode block : mth.getBasicBlocks()) {
InstructionRemover remover = new InstructionRemover(mth, block); remover.setBlock(block);
int size = block.getInstructions().size(); int size = block.getInstructions().size();
for (int i = 0; i < size; i++) { for (int i = 0; i < size; i++) {
InsnNode insn = block.getInstructions().get(i); InsnNode insn = block.getInstructions().get(i);
switch (insn.getType()) { switch (insn.getType()) {
case INVOKE: case INVOKE:
InvokeNode inv = (InvokeNode) insn; processInvoke(mth, block, i, remover);
MethodInfo callMth = inv.getCallMth();
if (callMth.isConstructor()) {
ConstructorInsn co = new ConstructorInsn(mth, inv);
removeInsnForArg(remover, co.getInstanceArg());
boolean remove = false;
if (co.isSuper() && (co.getArgsCount() == 0 || parentClass.isEnum())) {
remove = true;
} else if (co.isThis() && co.getArgsCount() == 0) {
MethodNode defCo = mth.getParentClass().searchMethodByName(callMth.getShortId());
if (defCo == null || defCo.isNoCode()) {
// default constructor not implemented
remove = true;
}
}
if (remove) {
remover.add(insn);
} else {
replaceInsn(block, i, co);
}
} else {
if (inv.getArgsCount() > 0) {
for (int j = 0; j < inv.getArgsCount(); j++) {
InsnArg arg = inv.getArg(j);
if (arg.isLiteral()) {
FieldNode f = parentClass.getConstFieldByLiteralArg((LiteralArg) arg);
if (f != null) {
arg.wrapInstruction(new IndexInsnNode(InsnType.SGET, f.getFieldInfo(), 0));
}
}
}
}
}
break; break;
case CONST: case CONST:
@@ -145,17 +118,134 @@ public class ModVisitor extends AbstractVisitor {
} }
} }
private static void processInvoke(MethodNode mth, BlockNode block, int insnNumber, InstructionRemover remover) {
ClassNode parentClass = mth.getParentClass();
InsnNode insn = block.getInstructions().get(insnNumber);
InvokeNode inv = (InvokeNode) insn;
MethodInfo callMth = inv.getCallMth();
if (callMth.isConstructor()) {
InsnNode instArgAssignInsn = ((RegisterArg) inv.getArg(0)).getAssignInsn();
ConstructorInsn co = new ConstructorInsn(mth, inv);
boolean remove = false;
if (co.isSuper() && (co.getArgsCount() == 0 || parentClass.isEnum())) {
remove = true;
} else if (co.isThis() && co.getArgsCount() == 0) {
MethodNode defCo = parentClass.searchMethodByName(callMth.getShortId());
if (defCo == null || defCo.isNoCode()) {
// default constructor not implemented
remove = true;
}
}
// remove super() call in instance initializer
if (parentClass.isAnonymous() && mth.isDefaultConstructor() && co.isSuper()) {
remove = true;
}
if (remove) {
remover.add(insn);
} else {
replaceInsn(block, insnNumber, co);
if (co.isNewInstance()) {
InsnNode newInstInsn = removeAssignChain(instArgAssignInsn, remover, InsnType.NEW_INSTANCE);
if (newInstInsn != null) {
RegisterArg instArg = newInstInsn.getResult();
RegisterArg resultArg = co.getResult();
if (!resultArg.equals(instArg)) {
// replace all usages of 'instArg' with result of this constructor instruction
for (RegisterArg useArg : new ArrayList<RegisterArg>(instArg.getSVar().getUseList())) {
RegisterArg dup = resultArg.duplicate();
InsnNode parentInsn = useArg.getParentInsn();
parentInsn.replaceArg(useArg, dup);
dup.setParentInsn(parentInsn);
resultArg.getSVar().use(dup);
}
}
}
}
ConstructorInsn replace = processConstructor(mth, co);
if (replace != null) {
replaceInsn(block, insnNumber, replace);
}
}
} else if (inv.getArgsCount() > 0) {
for (int j = 0; j < inv.getArgsCount(); j++) {
InsnArg arg = inv.getArg(j);
if (arg.isLiteral()) {
FieldNode f = parentClass.getConstFieldByLiteralArg((LiteralArg) arg);
if (f != null) {
arg.wrapInstruction(new IndexInsnNode(InsnType.SGET, f.getFieldInfo(), 0));
}
}
}
}
}
/**
* Replace call of synthetic constructor
*/
private static ConstructorInsn processConstructor(MethodNode mth, ConstructorInsn co) {
MethodNode callMth = mth.dex().resolveMethod(co.getCallMth());
if (callMth == null
|| !callMth.getAccessFlags().isSynthetic()
|| !allArgsNull(co)) {
return null;
}
ClassNode classNode = mth.dex().resolveClass(callMth.getParentClass().getClassInfo());
if (classNode == null) {
return null;
}
boolean passThis = co.getArgsCount() >= 1 && co.getArg(0).isThis();
String ctrId = "<init>(" + (passThis ? TypeGen.signature(co.getArg(0).getType()) : "") + ")V";
MethodNode defCtr = classNode.searchMethodByName(ctrId);
if (defCtr == null) {
return null;
}
ConstructorInsn newInsn = new ConstructorInsn(defCtr.getMethodInfo(), co.getCallType(), co.getInstanceArg());
newInsn.setResult(co.getResult());
return newInsn;
}
private static boolean allArgsNull(InsnNode insn) {
for (InsnArg insnArg : insn.getArguments()) {
if (insnArg.isLiteral()) {
LiteralArg lit = (LiteralArg) insnArg;
if (lit.getLiteral() != 0) {
return false;
}
} else if (!insnArg.isThis()) {
return false;
}
}
return true;
}
/**
* Remove instructions on 'move' chain until instruction with type 'insnType'
*/
private static InsnNode removeAssignChain(InsnNode insn, InstructionRemover remover, InsnType insnType) {
if (insn == null) {
return null;
}
remover.add(insn);
InsnType type = insn.getType();
if (type == insnType) {
return insn;
}
if (type == InsnType.MOVE) {
RegisterArg arg = (RegisterArg) insn.getArg(0);
return removeAssignChain(arg.getAssignInsn(), remover, insnType);
}
return null;
}
/** /**
* Remove unnecessary instructions * Remove unnecessary instructions
*/ */
private static void removeStep(MethodNode mth) { private static void removeStep(MethodNode mth, InstructionRemover remover) {
for (BlockNode block : mth.getBasicBlocks()) { for (BlockNode block : mth.getBasicBlocks()) {
InstructionRemover remover = new InstructionRemover(mth, block); remover.setBlock(block);
int size = block.getInstructions().size(); int size = block.getInstructions().size();
for (int i = 0; i < size; i++) { for (int i = 0; i < size; i++) {
InsnNode insn = block.getInstructions().get(i); InsnNode insn = block.getInstructions().get(i);
switch (insn.getType()) { switch (insn.getType()) {
case NOP: case NOP:
case GOTO: case GOTO:
@@ -201,6 +291,7 @@ public class ModVisitor extends AbstractVisitor {
} }
ExceptionHandler excHandler = handlerAttr.getHandler(); ExceptionHandler excHandler = handlerAttr.getHandler();
boolean noExitNode = true; // check if handler has exit edge to block not from this handler boolean noExitNode = true; // check if handler has exit edge to block not from this handler
boolean reThrow = false;
for (BlockNode excBlock : excHandler.getBlocks()) { for (BlockNode excBlock : excHandler.getBlocks()) {
if (noExitNode) { if (noExitNode) {
noExitNode = excHandler.getBlocks().containsAll(excBlock.getCleanSuccessors()); noExitNode = excHandler.getBlocks().containsAll(excBlock.getCleanSuccessors());
@@ -211,11 +302,11 @@ public class ModVisitor extends AbstractVisitor {
if (excHandler.isCatchAll() if (excHandler.isCatchAll()
&& size > 0 && size > 0
&& insns.get(size - 1).getType() == InsnType.THROW) { && insns.get(size - 1).getType() == InsnType.THROW) {
reThrow = true;
InstructionRemover.remove(mth, excBlock, size - 1); InstructionRemover.remove(mth, excBlock, size - 1);
// move not removed instructions to 'finally' block // move not removed instructions to 'finally' block
if (insns.size() != 0) { if (!insns.isEmpty()) {
// TODO: support instructions from several blocks // TODO: support instructions from several blocks
// tryBlock.setFinalBlockFromInsns(mth, insns); // tryBlock.setFinalBlockFromInsns(mth, insns);
// TODO: because of incomplete realization don't extract final block, // TODO: because of incomplete realization don't extract final block,
@@ -226,19 +317,36 @@ public class ModVisitor extends AbstractVisitor {
} }
List<InsnNode> blockInsns = block.getInstructions(); List<InsnNode> blockInsns = block.getInstructions();
if (blockInsns.size() > 0) { if (!blockInsns.isEmpty()) {
InsnNode insn = blockInsns.get(0); InsnNode insn = blockInsns.get(0);
if (insn.getType() == InsnType.MOVE_EXCEPTION if (insn.getType() == InsnType.MOVE_EXCEPTION) {
&& insn.getResult().getSVar().getUseCount() == 0) { // result arg used both in this insn and exception handler,
InstructionRemover.remove(mth, block, 0); RegisterArg resArg = insn.getResult();
ArgType type = excHandler.isCatchAll() ? ArgType.THROWABLE : excHandler.getCatchType().getType();
String name = excHandler.isCatchAll() ? "th" : "e";
if (resArg.getName() == null) {
resArg.setName(name);
}
SSAVar sVar = insn.getResult().getSVar();
if (sVar.getUseCount() == 0) {
excHandler.setArg(new NamedArg(name, type));
InstructionRemover.remove(mth, block, 0);
} else if (sVar.isUsedInPhi()) {
// exception var moved to external variable => replace with 'move' insn
InsnNode moveInsn = new InsnNode(InsnType.MOVE, 1);
moveInsn.setResult(insn.getResult());
NamedArg namedArg = new NamedArg(name, type);
moveInsn.addArg(namedArg);
excHandler.setArg(namedArg);
replaceInsn(block, 0, moveInsn);
}
} }
} }
int totalSize = 0; int totalSize = 0;
for (BlockNode excBlock : excHandler.getBlocks()) { for (BlockNode excBlock : excHandler.getBlocks()) {
totalSize += excBlock.getInstructions().size(); totalSize += excBlock.getInstructions().size();
} }
if (totalSize == 0 && noExitNode) { if (totalSize == 0 && noExitNode && reThrow) {
handlerAttr.getTryBlock().removeHandler(mth, excHandler); handlerAttr.getTryBlock().removeHandler(mth, excHandler);
} }
} }
@@ -253,16 +361,6 @@ public class ModVisitor extends AbstractVisitor {
block.getInstructions().set(i, insn); block.getInstructions().set(i, insn);
} }
/**
* In argument not used in other instructions then remove assign instruction.
*/
private static void removeInsnForArg(InstructionRemover remover, RegisterArg arg) {
if (arg.getSVar().getUseCount() == 0
&& arg.getAssignInsn() != null) {
remover.add(arg.getAssignInsn());
}
}
private static void checkArgsNames(MethodNode mth) { private static void checkArgsNames(MethodNode mth) {
for (RegisterArg arg : mth.getArguments(false)) { for (RegisterArg arg : mth.getArguments(false)) {
String name = arg.getName(); String name = arg.getName();
@@ -32,7 +32,7 @@ public class PrepareForCodeGen extends AbstractVisitor {
for (BlockNode block : blocks) { for (BlockNode block : blocks) {
removeInstructions(block); removeInstructions(block);
checkInline(block); checkInline(block);
removeParenthesis(block); // removeParenthesis(block);
modifyArith(block); modifyArith(block);
} }
} }
@@ -45,6 +45,7 @@ public class PrepareForCodeGen extends AbstractVisitor {
case NOP: case NOP:
case MONITOR_ENTER: case MONITOR_ENTER:
case MONITOR_EXIT: case MONITOR_EXIT:
case MOVE_EXCEPTION:
it.remove(); it.remove();
break; break;
@@ -123,25 +124,20 @@ public class PrepareForCodeGen extends AbstractVisitor {
*/ */
private static void modifyArith(BlockNode block) { private static void modifyArith(BlockNode block) {
List<InsnNode> list = block.getInstructions(); List<InsnNode> list = block.getInstructions();
for (int i = 0; i < list.size(); i++) { for (InsnNode insn : list) {
InsnNode insn = list.get(i); if (insn.getType() == InsnType.ARITH) {
if (insn.getType() != InsnType.ARITH) { RegisterArg res = insn.getResult();
continue; InsnArg arg = insn.getArg(0);
} boolean replace = false;
ArithNode arith = (ArithNode) insn; if (res.equals(arg)) {
RegisterArg res = arith.getResult(); replace = true;
InsnArg arg = arith.getArg(0); } else if (arg.isRegister()) {
boolean replace = false; RegisterArg regArg = (RegisterArg) arg;
replace = res.equalRegisterAndType(regArg);
if (res.equals(arg)) { }
replace = true; if (replace) {
} else if (arg.isRegister()) { insn.add(AFlag.ARITH_ONEARG);
RegisterArg regArg = (RegisterArg) arg; }
replace = res.equalRegisterAndType(regArg);
}
if (replace) {
ArithNode newArith = new ArithNode(arith.getOp(), res, arith.getArg(1));
list.set(i, newArith);
} }
} }
} }
@@ -1,12 +1,24 @@
package jadx.core.dex.visitors; package jadx.core.dex.visitors;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.EnumMapAttr;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.instructions.IndexInsnNode;
import jadx.core.dex.instructions.InsnType; import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.InvokeNode;
import jadx.core.dex.instructions.SwitchNode;
import jadx.core.dex.instructions.args.InsnArg; 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.LiteralArg;
import jadx.core.dex.nodes.BlockNode; import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.InstructionRemover; import jadx.core.utils.InstructionRemover;
import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.exceptions.JadxException; import jadx.core.utils.exceptions.JadxException;
import java.util.List; import java.util.List;
@@ -43,6 +55,9 @@ public class ReSugarCode extends AbstractVisitor {
case NEW_ARRAY: case NEW_ARRAY:
return processNewArray(mth, instructions, i, remover); return processNewArray(mth, instructions, i, remover);
case SWITCH:
return processEnumSwitch(mth, (SwitchNode) insn);
default: default:
return null; return null;
} }
@@ -75,4 +90,170 @@ public class ReSugarCode extends AbstractVisitor {
} }
return filledArr; return filledArr;
} }
private static InsnNode processEnumSwitch(MethodNode mth, SwitchNode insn) {
InsnArg arg = insn.getArg(0);
if (!arg.isInsnWrap()) {
return null;
}
InsnNode wrapInsn = ((InsnWrapArg) arg).getWrapInsn();
if (wrapInsn.getType() != InsnType.AGET) {
return null;
}
EnumMapInfo enumMapInfo = checkEnumMapAccess(mth, wrapInsn);
if (enumMapInfo == null) {
return null;
}
FieldNode enumMapField = enumMapInfo.getMapField();
InsnArg invArg = enumMapInfo.getArg();
EnumMapAttr.KeyValueMap valueMap = getEnumMap(mth, enumMapField);
if (valueMap == null) {
return null;
}
Object[] keys = insn.getKeys();
for (Object key : keys) {
Object newKey = valueMap.get(key);
if (newKey == null) {
return null;
}
}
// replace confirmed
if (!insn.replaceArg(arg, invArg)) {
return null;
}
for (int i = 0; i < keys.length; i++) {
keys[i] = valueMap.get(keys[i]);
}
enumMapField.add(AFlag.DONT_GENERATE);
checkAndHideClass(enumMapField.getParentClass());
return null;
}
private static EnumMapAttr.KeyValueMap getEnumMap(MethodNode mth, FieldNode field) {
ClassNode syntheticClass = field.getParentClass();
EnumMapAttr mapAttr = syntheticClass.get(AType.ENUM_MAP);
if (mapAttr != null) {
return mapAttr.getMap(field);
}
mapAttr = new EnumMapAttr();
syntheticClass.addAttr(mapAttr);
MethodNode clsInitMth = syntheticClass.searchMethodByName("<clinit>()V");
if (clsInitMth == null || clsInitMth.isNoCode()) {
return null;
}
if (clsInitMth.getBasicBlocks() == null) {
try {
clsInitMth.load();
} catch (DecodeException e) {
LOG.error("Load failed", e);
return null;
}
if (clsInitMth.getBasicBlocks() == null) {
// TODO:
return null;
}
}
for (BlockNode block : clsInitMth.getBasicBlocks()) {
for (InsnNode insn : block.getInstructions()) {
if (insn.getType() == InsnType.APUT) {
addToEnumMap(mth, mapAttr, insn);
}
}
}
return mapAttr.getMap(field);
}
private static void addToEnumMap(MethodNode mth, EnumMapAttr mapAttr, InsnNode aputInsn) {
InsnArg litArg = aputInsn.getArg(2);
if (!litArg.isLiteral()) {
return;
}
EnumMapInfo mapInfo = checkEnumMapAccess(mth, aputInsn);
if (mapInfo == null) {
return;
}
InsnArg enumArg = mapInfo.getArg();
FieldNode field = mapInfo.getMapField();
if (field == null || !enumArg.isInsnWrap()) {
return;
}
InsnNode sget = ((InsnWrapArg) enumArg).getWrapInsn();
if (!(sget instanceof IndexInsnNode)) {
return;
}
Object index = ((IndexInsnNode) sget).getIndex();
if (!(index instanceof FieldInfo)) {
return;
}
FieldNode fieldNode = mth.dex().resolveField((FieldInfo) index);
if (fieldNode == null) {
return;
}
int literal = (int) ((LiteralArg) litArg).getLiteral();
mapAttr.add(field, literal, fieldNode);
}
public static EnumMapInfo checkEnumMapAccess(MethodNode mth, InsnNode checkInsn) {
InsnArg sgetArg = checkInsn.getArg(0);
InsnArg invArg = checkInsn.getArg(1);
if (!sgetArg.isInsnWrap() || !invArg.isInsnWrap()) {
return null;
}
InsnNode invInsn = ((InsnWrapArg) invArg).getWrapInsn();
InsnNode sgetInsn = ((InsnWrapArg) sgetArg).getWrapInsn();
if (invInsn.getType() != InsnType.INVOKE || sgetInsn.getType() != InsnType.SGET) {
return null;
}
InvokeNode inv = (InvokeNode) invInsn;
if (!inv.getCallMth().getShortId().equals("ordinal()I")) {
return null;
}
ClassNode enumCls = mth.dex().resolveClass(inv.getCallMth().getDeclClass());
if (enumCls == null || !enumCls.isEnum()) {
return null;
}
Object index = ((IndexInsnNode) sgetInsn).getIndex();
if (!(index instanceof FieldInfo)) {
return null;
}
FieldNode enumMapField = mth.dex().resolveField((FieldInfo) index);
if (enumMapField == null || !enumMapField.getAccessFlags().isSynthetic()) {
return null;
}
return new EnumMapInfo(inv.getArg(0), enumMapField);
}
/**
* If all static final synthetic fields have DONT_GENERATE => hide whole class
*/
private static void checkAndHideClass(ClassNode cls) {
for (FieldNode field : cls.getFields()) {
AccessInfo af = field.getAccessFlags();
if (af.isSynthetic() && af.isStatic() && af.isFinal()
&& !field.contains(AFlag.DONT_GENERATE)) {
return;
}
}
cls.add(AFlag.DONT_GENERATE);
}
private static class EnumMapInfo {
private final InsnArg arg;
private final FieldNode mapField;
public EnumMapInfo(InsnArg arg, FieldNode mapField) {
this.arg = arg;
this.mapField = mapField;
}
public InsnArg getArg() {
return arg;
}
public FieldNode getMapField() {
return mapField;
}
}
} }
@@ -15,9 +15,11 @@ import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.InsnWrapArg; import jadx.core.dex.instructions.args.InsnWrapArg;
import jadx.core.dex.instructions.args.LiteralArg; import jadx.core.dex.instructions.args.LiteralArg;
import jadx.core.dex.instructions.mods.ConstructorInsn; 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.BlockNode;
import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.regions.conditions.IfCondition;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
@@ -62,6 +64,9 @@ public class SimplifyVisitor extends AbstractVisitor {
case IF: case IF:
simplifyIf((IfNode) insn); simplifyIf((IfNode) insn);
break; break;
case TERNARY:
simplifyTernary((TernaryInsn) insn);
break;
case INVOKE: case INVOKE:
return convertInvoke(mth, insn); return convertInvoke(mth, insn);
@@ -99,12 +104,24 @@ public class SimplifyVisitor extends AbstractVisitor {
&& ((LiteralArg) insn.getArg(1)).getLiteral() == 0) { && ((LiteralArg) insn.getArg(1)).getLiteral() == 0) {
insn.changeCondition(insn.getOp(), wi.getArg(0), wi.getArg(1)); insn.changeCondition(insn.getOp(), wi.getArg(0), wi.getArg(1));
} else { } else {
LOG.warn("TODO: cmp" + insn); LOG.warn("TODO: cmp {}", insn);
} }
} }
} }
} }
/**
* Simplify condition in ternary operation
*/
private static void simplifyTernary(TernaryInsn insn) {
IfCondition condition = insn.getCondition();
if (condition.isCompare()) {
simplifyIf(condition.getCompare().getInsn());
} else {
insn.simplifyCondition();
}
}
private static InsnNode convertInvoke(MethodNode mth, InsnNode insn) { private static InsnNode convertInvoke(MethodNode mth, InsnNode insn) {
MethodInfo callMth = ((InvokeNode) insn).getCallMth(); MethodInfo callMth = ((InvokeNode) insn).getCallMth();
if (callMth.getDeclClass().getFullName().equals(Consts.CLASS_STRING_BUILDER) if (callMth.getDeclClass().getFullName().equals(Consts.CLASS_STRING_BUILDER)
@@ -6,7 +6,7 @@ import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.IBlock; import jadx.core.dex.nodes.IBlock;
import jadx.core.dex.nodes.IRegion; import jadx.core.dex.nodes.IRegion;
import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.regions.LoopRegion; import jadx.core.dex.regions.loops.LoopRegion;
import jadx.core.dex.visitors.AbstractVisitor; import jadx.core.dex.visitors.AbstractVisitor;
import jadx.core.utils.ErrorsCounter; import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.exceptions.JadxException; import jadx.core.utils.exceptions.JadxException;
@@ -72,7 +72,6 @@ public class CheckRegions extends AbstractVisitor {
BlockNode loopHeader = ((LoopRegion) region).getHeader(); BlockNode loopHeader = ((LoopRegion) region).getHeader();
if (loopHeader != null && loopHeader.getInstructions().size() != 1) { if (loopHeader != null && loopHeader.getInstructions().size() != 1) {
ErrorsCounter.methodError(mth, "Incorrect condition in loop: " + loopHeader); ErrorsCounter.methodError(mth, "Incorrect condition in loop: " + loopHeader);
mth.add(AFlag.INCONSISTENT_CODE);
} }
} }
} }
@@ -2,6 +2,7 @@ package jadx.core.dex.visitors.regions;
import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.LoopInfo;
import jadx.core.dex.instructions.IfNode; import jadx.core.dex.instructions.IfNode;
import jadx.core.dex.instructions.InsnType; import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.args.InsnArg; import jadx.core.dex.instructions.args.InsnArg;
@@ -9,9 +10,9 @@ import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.BlockNode; import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.regions.IfCondition; import jadx.core.dex.regions.conditions.IfCondition;
import jadx.core.dex.regions.IfCondition.Mode; import jadx.core.dex.regions.conditions.IfCondition.Mode;
import jadx.core.dex.regions.IfInfo; import jadx.core.dex.regions.conditions.IfInfo;
import jadx.core.utils.BlockUtils; import jadx.core.utils.BlockUtils;
import java.util.Collection; import java.util.Collection;
@@ -21,6 +22,7 @@ import java.util.Set;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import static jadx.core.utils.BlockUtils.getNextBlock;
import static jadx.core.utils.BlockUtils.isPathExists; import static jadx.core.utils.BlockUtils.isPathExists;
public class IfMakerHelper { public class IfMakerHelper {
@@ -38,6 +40,11 @@ public class IfMakerHelper {
return info; return info;
} }
static IfInfo searchNestedIf(IfInfo info) {
IfInfo tmp = mergeNestedIfNodes(info);
return tmp != null ? tmp : info;
}
static IfInfo restructureIf(MethodNode mth, BlockNode block, IfInfo info) { static IfInfo restructureIf(MethodNode mth, BlockNode block, IfInfo info) {
final BlockNode thenBlock = info.getThenBlock(); final BlockNode thenBlock = info.getThenBlock();
final BlockNode elseBlock = info.getElseBlock(); final BlockNode elseBlock = info.getElseBlock();
@@ -47,18 +54,18 @@ public class IfMakerHelper {
info.setOutBlock(null); info.setOutBlock(null);
return info; return info;
} }
boolean badThen = !allPathsFromIf(thenBlock, info); boolean badThen = isBadBranchBlock(info, thenBlock);
boolean badElse = !allPathsFromIf(elseBlock, info); boolean badElse = isBadBranchBlock(info, elseBlock);
if (badThen && badElse) { if (badThen && badElse) {
LOG.debug("Stop processing blocks after 'if': {}, method: {}", info, mth); LOG.debug("Stop processing blocks after 'if': {}, method: {}", info.getIfBlock(), mth);
return null; return null;
} }
if (badElse) { if (badElse) {
info = new IfInfo(info.getCondition(), thenBlock, null); info = new IfInfo(info, thenBlock, null);
info.setOutBlock(elseBlock); info.setOutBlock(elseBlock);
} else if (badThen) { } else if (badThen) {
info = IfInfo.invert(info); info = IfInfo.invert(info);
info = new IfInfo(info.getCondition(), elseBlock, null); info = new IfInfo(info, elseBlock, null);
info.setOutBlock(thenBlock); info.setOutBlock(thenBlock);
} else { } else {
List<BlockNode> thenSC = thenBlock.getCleanSuccessors(); List<BlockNode> thenSC = thenBlock.getCleanSuccessors();
@@ -86,6 +93,26 @@ public class IfMakerHelper {
return info; return info;
} }
private static boolean isBadBranchBlock(IfInfo info, BlockNode block) {
// check if block at end of loop edge
if (block.contains(AFlag.LOOP_START) && block.getPredecessors().size() == 1) {
BlockNode pred = block.getPredecessors().get(0);
if (pred.contains(AFlag.LOOP_END)) {
List<LoopInfo> startLoops = block.getAll(AType.LOOP);
List<LoopInfo> endLoops = pred.getAll(AType.LOOP);
// search for same loop
for (LoopInfo startLoop : startLoops) {
for (LoopInfo endLoop : endLoops) {
if (startLoop == endLoop) {
return true;
}
}
}
}
}
return !allPathsFromIf(block, info);
}
private static boolean allPathsFromIf(BlockNode block, IfInfo info) { private static boolean allPathsFromIf(BlockNode block, IfInfo info) {
List<BlockNode> preds = block.getPredecessors(); List<BlockNode> preds = block.getPredecessors();
Set<BlockNode> ifBlocks = info.getMergedBlocks(); Set<BlockNode> ifBlocks = info.getMergedBlocks();
@@ -126,12 +153,13 @@ public class IfMakerHelper {
if (!RegionMaker.isEqualPaths(curElse, nextIf.getElseBlock()) if (!RegionMaker.isEqualPaths(curElse, nextIf.getElseBlock())
&& !RegionMaker.isEqualPaths(curThen, nextIf.getThenBlock())) { && !RegionMaker.isEqualPaths(curThen, nextIf.getThenBlock())) {
// complex condition, run additional checks // complex condition, run additional checks
if (checkConditionBranches(curThen, curElse) || checkConditionBranches(curElse, curThen)) { if (checkConditionBranches(curThen, curElse)
|| checkConditionBranches(curElse, curThen)) {
return null; return null;
} }
BlockNode otherBranchBlock = followThenBranch ? curElse : curThen; BlockNode otherBranchBlock = followThenBranch ? curElse : curThen;
if (!isPathExists(nextIf.getIfBlock(), otherBranchBlock)) { if (!isPathExists(nextIf.getIfBlock(), otherBranchBlock)) {
return null; return checkForTernaryInCondition(currentIf);
} }
if (isPathExists(nextIf.getThenBlock(), otherBranchBlock) if (isPathExists(nextIf.getThenBlock(), otherBranchBlock)
&& isPathExists(nextIf.getElseBlock(), otherBranchBlock)) { && isPathExists(nextIf.getElseBlock(), otherBranchBlock)) {
@@ -147,15 +175,46 @@ public class IfMakerHelper {
if (isInversionNeeded(currentIf, nextIf)) { if (isInversionNeeded(currentIf, nextIf)) {
nextIf = IfInfo.invert(nextIf); nextIf = IfInfo.invert(nextIf);
} }
} else {
return currentIf;
} }
} }
IfInfo result = mergeIfInfo(currentIf, nextIf, followThenBranch); IfInfo result = mergeIfInfo(currentIf, nextIf, followThenBranch);
// search next nested if block // search next nested if block
IfInfo next = mergeNestedIfNodes(result); return searchNestedIf(result);
if (next != null) { }
return next;
private static IfInfo checkForTernaryInCondition(IfInfo currentIf) {
IfInfo nextThen = getNextIf(currentIf, currentIf.getThenBlock());
IfInfo nextElse = getNextIf(currentIf, currentIf.getElseBlock());
if (nextThen == null || nextElse == null) {
return null;
} }
if (!nextThen.getIfBlock().getDomFrontier().equals(nextElse.getIfBlock().getDomFrontier())) {
return null;
}
nextThen = searchNestedIf(nextThen);
nextElse = searchNestedIf(nextElse);
if (nextThen.getThenBlock() == nextElse.getThenBlock()
&& nextThen.getElseBlock() == nextElse.getElseBlock()) {
return mergeTernaryConditions(currentIf, nextThen, nextElse);
}
if (nextThen.getThenBlock() == nextElse.getElseBlock()
&& nextThen.getElseBlock() == nextElse.getThenBlock()) {
nextElse = IfInfo.invert(nextElse);
return mergeTernaryConditions(currentIf, nextThen, nextElse);
}
return null;
}
private static IfInfo mergeTernaryConditions(IfInfo currentIf, IfInfo nextThen, IfInfo nextElse) {
IfCondition newCondition = IfCondition.ternary(currentIf.getCondition(),
nextThen.getCondition(), nextElse.getCondition());
IfInfo result = new IfInfo(newCondition, nextThen.getThenBlock(), nextThen.getElseBlock());
result.setIfBlock(currentIf.getIfBlock());
result.merge(currentIf, nextThen, nextElse);
confirmMerge(result);
return result; return result;
} }
@@ -170,19 +229,33 @@ public class IfMakerHelper {
private static IfInfo mergeIfInfo(IfInfo first, IfInfo second, boolean followThenBranch) { private static IfInfo mergeIfInfo(IfInfo first, IfInfo second, boolean followThenBranch) {
Mode mergeOperation = followThenBranch ? Mode.AND : Mode.OR; Mode mergeOperation = followThenBranch ? Mode.AND : Mode.OR;
BlockNode otherPathBlock = followThenBranch ? first.getElseBlock() : first.getThenBlock();
RegionMaker.skipSimplePath(otherPathBlock);
first.getIfBlock().add(AFlag.SKIP);
second.getIfBlock().add(AFlag.SKIP);
IfCondition condition = IfCondition.merge(mergeOperation, first.getCondition(), second.getCondition()); IfCondition condition = IfCondition.merge(mergeOperation, first.getCondition(), second.getCondition());
IfInfo result = new IfInfo(condition, second); IfInfo result = new IfInfo(condition, second);
result.setIfBlock(first.getIfBlock()); result.setIfBlock(first.getIfBlock());
result.getMergedBlocks().addAll(first.getMergedBlocks()); result.merge(first, second);
result.getMergedBlocks().addAll(second.getMergedBlocks());
BlockNode otherPathBlock = followThenBranch ? first.getElseBlock() : first.getThenBlock();
skipSimplePath(otherPathBlock, result.getSkipBlocks());
return result; return result;
} }
static void confirmMerge(IfInfo info) {
if (info.getMergedBlocks().size() > 1) {
for (BlockNode block : info.getMergedBlocks()) {
if (block != info.getIfBlock()) {
block.add(AFlag.SKIP);
}
}
}
if (!info.getSkipBlocks().isEmpty()) {
for (BlockNode block : info.getSkipBlocks()) {
block.add(AFlag.SKIP);
}
info.getSkipBlocks().clear();
}
}
private static IfInfo getNextIf(IfInfo info, BlockNode block) { private static IfInfo getNextIf(IfInfo info, BlockNode block) {
if (!canSelectNext(info, block)) { if (!canSelectNext(info, block)) {
return null; return null;
@@ -250,4 +323,13 @@ public class IfMakerHelper {
} }
return null; return null;
} }
private static void skipSimplePath(BlockNode block, Set<BlockNode> skipped) {
while (block != null
&& block.getCleanSuccessors().size() < 2
&& block.getPredecessors().size() == 1) {
skipped.add(block);
block = getNextBlock(block);
}
}
} }
@@ -6,10 +6,10 @@ import jadx.core.dex.nodes.IBlock;
import jadx.core.dex.nodes.IContainer; import jadx.core.dex.nodes.IContainer;
import jadx.core.dex.nodes.IRegion; import jadx.core.dex.nodes.IRegion;
import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.regions.IfCondition;
import jadx.core.dex.regions.IfCondition.Mode;
import jadx.core.dex.regions.IfRegion;
import jadx.core.dex.regions.Region; import jadx.core.dex.regions.Region;
import jadx.core.dex.regions.conditions.IfCondition;
import jadx.core.dex.regions.conditions.IfCondition.Mode;
import jadx.core.dex.regions.conditions.IfRegion;
import jadx.core.dex.visitors.AbstractVisitor; import jadx.core.dex.visitors.AbstractVisitor;
import jadx.core.utils.RegionUtils; import jadx.core.utils.RegionUtils;
@@ -21,6 +21,16 @@ public class IfRegionVisitor extends AbstractVisitor implements IRegionVisitor,
@Override @Override
public void visit(MethodNode mth) { public void visit(MethodNode mth) {
// collapse ternary operators
DepthRegionTraversal.traverseAllIterative(mth, new IRegionIterativeVisitor() {
@Override
public boolean visitRegion(MethodNode mth, IRegion region) {
if (region instanceof IfRegion) {
return TernaryMod.makeTernaryInsn(mth, (IfRegion) region);
}
return false;
}
});
DepthRegionTraversal.traverseAll(mth, this); DepthRegionTraversal.traverseAll(mth, this);
DepthRegionTraversal.traverseAllIterative(mth, this); DepthRegionTraversal.traverseAllIterative(mth, this);
} }
@@ -35,7 +45,7 @@ public class IfRegionVisitor extends AbstractVisitor implements IRegionVisitor,
@Override @Override
public boolean visitRegion(MethodNode mth, IRegion region) { public boolean visitRegion(MethodNode mth, IRegion region) {
if (region instanceof IfRegion) { if (region instanceof IfRegion) {
return removeRedundantElseBlock((IfRegion) region); return removeRedundantElseBlock(mth, (IfRegion) region);
} }
return false; return false;
} }
@@ -53,8 +63,6 @@ public class IfRegionVisitor extends AbstractVisitor implements IRegionVisitor,
moveReturnToThenBlock(mth, ifRegion); moveReturnToThenBlock(mth, ifRegion);
moveBreakToThenBlock(ifRegion); moveBreakToThenBlock(ifRegion);
markElseIfChains(ifRegion); markElseIfChains(ifRegion);
TernaryMod.makeTernaryInsn(mth, ifRegion);
} }
private static void simplifyIfCondition(IfRegion ifRegion) { private static void simplifyIfCondition(IfRegion ifRegion) {
@@ -128,28 +136,38 @@ public class IfRegionVisitor extends AbstractVisitor implements IRegionVisitor,
} }
} }
private static boolean removeRedundantElseBlock(IfRegion ifRegion) { private static boolean removeRedundantElseBlock(MethodNode mth, IfRegion ifRegion) {
if (ifRegion.getElseRegion() != null if (ifRegion.getElseRegion() == null
&& !ifRegion.contains(AFlag.ELSE_IF_CHAIN) || ifRegion.contains(AFlag.ELSE_IF_CHAIN)
&& !ifRegion.getElseRegion().contains(AFlag.ELSE_IF_CHAIN) || ifRegion.getElseRegion().contains(AFlag.ELSE_IF_CHAIN)) {
&& hasBranchTerminator(ifRegion) return false;
&& insnsCount(ifRegion.getThenRegion()) < 2) { }
IRegion parent = ifRegion.getParent(); if (!hasBranchTerminator(ifRegion.getThenRegion())) {
Region newRegion = new Region(parent); return false;
if (parent.replaceSubBlock(ifRegion, newRegion)) { }
newRegion.add(ifRegion); // code style check:
newRegion.add(ifRegion.getElseRegion()); // will remove 'return;' from 'then' and 'else' with one instruction
ifRegion.setElseRegion(null); // see #jadx.tests.integration.conditions.TestConditions9
return true; if (mth.getReturnType() == ArgType.VOID
} && insnsCount(ifRegion.getThenRegion()) == 2
&& insnsCount(ifRegion.getElseRegion()) == 2) {
return false;
}
IRegion parent = ifRegion.getParent();
Region newRegion = new Region(parent);
if (parent.replaceSubBlock(ifRegion, newRegion)) {
newRegion.add(ifRegion);
newRegion.add(ifRegion.getElseRegion());
ifRegion.setElseRegion(null);
return true;
} }
return false; return false;
} }
private static boolean hasBranchTerminator(IfRegion ifRegion) { private static boolean hasBranchTerminator(IContainer region) {
// TODO: check for exception throw // TODO: check for exception throw
return RegionUtils.hasExitBlock(ifRegion.getThenRegion()) return RegionUtils.hasExitBlock(region)
|| RegionUtils.hasBreakInsn(ifRegion.getThenRegion()); || RegionUtils.hasBreakInsn(region);
} }
private static void invertIfRegion(IfRegion ifRegion) { private static void invertIfRegion(IfRegion ifRegion) {
@@ -0,0 +1,356 @@
package jadx.core.dex.visitors.regions;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.ArithNode;
import jadx.core.dex.instructions.ArithOp;
import jadx.core.dex.instructions.IfOp;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.InvokeNode;
import jadx.core.dex.instructions.InvokeType;
import jadx.core.dex.instructions.PhiInsn;
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.instructions.args.SSAVar;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.IBlock;
import jadx.core.dex.nodes.IRegion;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.regions.conditions.Compare;
import jadx.core.dex.regions.conditions.IfCondition;
import jadx.core.dex.regions.loops.ForEachLoop;
import jadx.core.dex.regions.loops.ForLoop;
import jadx.core.dex.regions.loops.LoopRegion;
import jadx.core.dex.regions.loops.LoopType;
import jadx.core.dex.visitors.AbstractVisitor;
import jadx.core.dex.visitors.CodeShrinker;
import jadx.core.utils.BlockUtils;
import jadx.core.utils.InstructionRemover;
import jadx.core.utils.RegionUtils;
import java.util.LinkedList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LoopRegionVisitor extends AbstractVisitor implements IRegionVisitor {
private static final Logger LOG = LoggerFactory.getLogger(LoopRegionVisitor.class);
@Override
public void visit(MethodNode mth) {
DepthRegionTraversal.traverseAll(mth, this);
}
@Override
public void enterRegion(MethodNode mth, IRegion region) {
if (region instanceof LoopRegion) {
processLoopRegion(mth, (LoopRegion) region);
}
}
private static void processLoopRegion(MethodNode mth, LoopRegion loopRegion) {
if (loopRegion.isConditionAtEnd()) {
return;
}
IfCondition condition = loopRegion.getCondition();
if (condition == null) {
return;
}
if (checkForIndexedLoop(mth, loopRegion, condition)) {
return;
}
if (checkIterableForEach(mth, loopRegion, condition)) {
return;
}
}
/**
* Check for indexed loop.
*/
private static boolean checkForIndexedLoop(MethodNode mth, LoopRegion loopRegion, IfCondition condition) {
InsnNode incrInsn = RegionUtils.getLastInsn(loopRegion);
if (incrInsn == null) {
return false;
}
RegisterArg incrArg = incrInsn.getResult();
if (incrArg == null
|| incrArg.getSVar() == null
|| !incrArg.getSVar().isUsedInPhi()) {
return false;
}
PhiInsn phiInsn = incrArg.getSVar().getUsedInPhi();
if (phiInsn == null
|| phiInsn.getArgsCount() != 2
|| !phiInsn.getArg(1).equals(incrArg)
|| incrArg.getSVar().getUseCount() != 1) {
return false;
}
RegisterArg arg = phiInsn.getResult();
List<RegisterArg> condArgs = condition.getRegisterArgs();
if (!condArgs.contains(arg) || arg.getSVar().isUsedInPhi()) {
return false;
}
RegisterArg initArg = phiInsn.getArg(0);
InsnNode initInsn = initArg.getAssignInsn();
if (initInsn == null || initArg.getSVar().getUseCount() != 1) {
return false;
}
if (!usedOnlyInLoop(mth, loopRegion, arg)) {
return false;
}
// can't make loop if argument from increment instruction is assign in loop
List<RegisterArg> args = new LinkedList<RegisterArg>();
incrInsn.getRegisterArgs(args);
for (RegisterArg iArg : args) {
if (assignOnlyInLoop(mth, loopRegion, iArg)) {
return false;
}
}
// all checks passed
initInsn.add(AFlag.SKIP);
incrInsn.add(AFlag.SKIP);
LoopType arrForEach = checkArrayForEach(mth, initInsn, incrInsn, condition);
if (arrForEach != null) {
loopRegion.setType(arrForEach);
return true;
}
loopRegion.setType(new ForLoop(initInsn, incrInsn));
return true;
}
private static LoopType checkArrayForEach(MethodNode mth, InsnNode initInsn, InsnNode incrInsn, IfCondition condition) {
if (!(incrInsn instanceof ArithNode)) {
return null;
}
ArithNode arithNode = (ArithNode) incrInsn;
if (arithNode.getOp() != ArithOp.ADD) {
return null;
}
InsnArg lit = incrInsn.getArg(1);
if (!lit.isLiteral() || ((LiteralArg) lit).getLiteral() != 1) {
return null;
}
if (initInsn.getType() != InsnType.CONST
|| !initInsn.getArg(0).isLiteral()
|| ((LiteralArg) initInsn.getArg(0)).getLiteral() != 0) {
return null;
}
InsnArg condArg = incrInsn.getArg(0);
if (!condArg.isRegister()) {
return null;
}
SSAVar sVar = ((RegisterArg) condArg).getSVar();
List<RegisterArg> args = sVar.getUseList();
if (args.size() != 3 || args.get(2) != condArg) {
return null;
}
condArg = args.get(0);
RegisterArg arrIndex = args.get(1);
InsnNode arrGetInsn = arrIndex.getParentInsn();
if (arrGetInsn == null || arrGetInsn.getType() != InsnType.AGET) {
return null;
}
if (!condition.isCompare()) {
return null;
}
Compare compare = condition.getCompare();
if (compare.getOp() != IfOp.LT || compare.getA() != condArg) {
return null;
}
InsnNode len;
InsnArg bCondArg = compare.getB();
if (bCondArg.isInsnWrap()) {
len = ((InsnWrapArg) bCondArg).getWrapInsn();
} else if (bCondArg.isRegister()) {
len = ((RegisterArg) bCondArg).getAssignInsn();
} else {
return null;
}
if (len == null || len.getType() != InsnType.ARRAY_LENGTH) {
return null;
}
InsnArg arrayArg = len.getArg(0);
if (!arrayArg.equals(arrGetInsn.getArg(0))) {
return null;
}
RegisterArg iterVar = arrGetInsn.getResult();
if (iterVar == null) {
return null;
}
// array for each loop confirmed
len.add(AFlag.SKIP);
arrGetInsn.add(AFlag.SKIP);
InstructionRemover.unbindInsn(mth, len);
// inline array variable
CodeShrinker.shrinkMethod(mth);
if (arrGetInsn.contains(AFlag.WRAPPED)) {
InsnArg wrapArg = BlockUtils.searchWrappedInsnParent(mth, arrGetInsn);
if (wrapArg != null) {
wrapArg.getParentInsn().replaceArg(wrapArg, iterVar);
} else {
LOG.debug(" checkArrayForEach: Wrapped insn not found: {}, mth: {}", arrGetInsn, mth);
}
}
return new ForEachLoop(iterVar, len.getArg(0));
}
private static boolean checkIterableForEach(MethodNode mth, LoopRegion loopRegion, IfCondition condition) {
List<RegisterArg> condArgs = condition.getRegisterArgs();
if (condArgs.size() != 1) {
return false;
}
RegisterArg iteratorArg = condArgs.get(0);
SSAVar sVar = iteratorArg.getSVar();
if (sVar == null || sVar.isUsedInPhi()) {
return false;
}
List<RegisterArg> useList = sVar.getUseList();
InsnNode assignInsn = iteratorArg.getAssignInsn();
if (useList.size() != 2
|| assignInsn == null
|| !checkInvoke(assignInsn, null, "iterator()Ljava/util/Iterator;", 0)) {
return false;
}
InsnArg iterableArg = assignInsn.getArg(0);
InsnNode hasNextCall = useList.get(0).getParentInsn();
InsnNode nextCall = useList.get(1).getParentInsn();
if (!checkInvoke(hasNextCall, "java.util.Iterator", "hasNext()Z", 0)
|| !checkInvoke(nextCall, "java.util.Iterator", "next()Ljava/lang/Object;", 0)) {
return false;
}
List<InsnNode> toSkip = new LinkedList<InsnNode>();
RegisterArg iterVar = nextCall.getResult();
if (nextCall.contains(AFlag.WRAPPED)) {
InsnArg wrapArg = BlockUtils.searchWrappedInsnParent(mth, nextCall);
if (wrapArg != null) {
InsnNode parentInsn = wrapArg.getParentInsn();
if (parentInsn.getType() != InsnType.CHECK_CAST) {
parentInsn.replaceArg(wrapArg, iterVar);
} else {
iterVar = parentInsn.getResult();
InsnArg castArg = BlockUtils.searchWrappedInsnParent(mth, parentInsn);
if (castArg != null) {
castArg.getParentInsn().replaceArg(castArg, iterVar);
} else {
// cast not inlined
toSkip.add(parentInsn);
}
}
} else {
LOG.warn(" checkIterableForEach: Wrapped insn not found: {}, mth: {}", nextCall, mth);
return false;
}
} else {
toSkip.add(nextCall);
}
if (iterVar == null || !fixIterableType(iterableArg, iterVar)) {
return false;
}
assignInsn.add(AFlag.SKIP);
for (InsnNode insnNode : toSkip) {
insnNode.add(AFlag.SKIP);
}
loopRegion.setType(new ForEachLoop(iterVar, iterableArg));
return true;
}
private static boolean fixIterableType(InsnArg iterableArg, RegisterArg iterVar) {
ArgType type = iterableArg.getType();
if (type.isGeneric()) {
ArgType[] genericTypes = type.getGenericTypes();
if (genericTypes != null && genericTypes.length == 1) {
ArgType gType = genericTypes[0];
if (ArgType.isInstanceOf(gType, iterVar.getType())) {
return true;
} else {
LOG.warn("Generic type differs: {} and {}", type, iterVar.getType());
}
}
} else {
if (!iterableArg.isRegister()) {
return true;
}
// TODO: add checks
type = ArgType.generic(type.getObject(), new ArgType[]{iterVar.getType()});
iterableArg.setType(type);
return true;
}
return false;
}
/**
* Check if instruction is a interface invoke with corresponding parameters.
*/
private static boolean checkInvoke(InsnNode insn, String declClsFullName, String mthId, int argsCount) {
if (insn.getType() == InsnType.INVOKE) {
InvokeNode inv = (InvokeNode) insn;
MethodInfo callMth = inv.getCallMth();
if (callMth.getArgsCount() == argsCount
&& callMth.getShortId().equals(mthId)
&& inv.getInvokeType() == InvokeType.INTERFACE) {
return declClsFullName == null || callMth.getDeclClass().getFullName().equals(declClsFullName);
}
}
return false;
}
private static boolean assignOnlyInLoop(MethodNode mth, LoopRegion loopRegion, RegisterArg arg) {
InsnNode assignInsn = arg.getAssignInsn();
if (assignInsn == null) {
return true;
}
if (!argInLoop(mth, loopRegion, assignInsn.getResult())) {
return false;
}
if (assignInsn instanceof PhiInsn) {
PhiInsn phiInsn = (PhiInsn) assignInsn;
for (InsnArg phiArg : phiInsn.getArguments()) {
if (!assignOnlyInLoop(mth, loopRegion, (RegisterArg) phiArg)) {
return false;
}
}
}
return true;
}
private static boolean usedOnlyInLoop(MethodNode mth, LoopRegion loopRegion, RegisterArg arg) {
List<RegisterArg> useList = arg.getSVar().getUseList();
for (RegisterArg useArg : useList) {
if (!argInLoop(mth, loopRegion, useArg)) {
return false;
}
}
return true;
}
private static boolean argInLoop(MethodNode mth, LoopRegion loopRegion, RegisterArg arg) {
InsnNode parentInsn = arg.getParentInsn();
if (parentInsn == null) {
return false;
}
BlockNode block = BlockUtils.getBlockByInsn(mth, parentInsn);
if (block == null) {
LOG.debug(" LoopRegionVisitor: instruction not found: {}, mth: {}", parentInsn, mth);
return false;
}
return RegionUtils.isRegionContainsBlock(loopRegion, block);
}
@Override
public void leaveRegion(MethodNode mth, IRegion region) {
}
@Override
public void processBlock(MethodNode mth, IBlock container) {
}
}
@@ -1,6 +1,5 @@
package jadx.core.dex.visitors.regions; package jadx.core.dex.visitors.regions;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.AType;
import jadx.core.dex.nodes.BlockNode; import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.IContainer; import jadx.core.dex.nodes.IContainer;
@@ -8,10 +7,13 @@ import jadx.core.dex.nodes.IRegion;
import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.regions.AbstractRegion; import jadx.core.dex.regions.AbstractRegion;
import jadx.core.dex.regions.Region; import jadx.core.dex.regions.Region;
import jadx.core.dex.regions.TryCatchRegion;
import jadx.core.dex.regions.loops.LoopRegion;
import jadx.core.dex.trycatch.CatchAttr; import jadx.core.dex.trycatch.CatchAttr;
import jadx.core.dex.trycatch.ExceptionHandler; import jadx.core.dex.trycatch.ExceptionHandler;
import jadx.core.dex.trycatch.TryCatchBlock; import jadx.core.dex.trycatch.TryCatchBlock;
import jadx.core.utils.BlockUtils; import jadx.core.utils.BlockUtils;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.RegionUtils; import jadx.core.utils.RegionUtils;
import jadx.core.utils.exceptions.JadxRuntimeException; import jadx.core.utils.exceptions.JadxRuntimeException;
@@ -78,6 +80,10 @@ public class ProcessTryCatchRegions extends AbstractRegionVisitor {
} }
} }
} }
if (bs == null) {
LOG.debug(" Can't build try/catch dominators bitset, tb: {}, mth: {} ", tb, mth);
continue;
}
// intersect to get dominator of dominators // intersect to get dominator of dominators
List<BlockNode> domBlocks = BlockUtils.bitSetToBlocks(mth, bs); List<BlockNode> domBlocks = BlockUtils.bitSetToBlocks(mth, bs);
@@ -94,7 +100,7 @@ public class ProcessTryCatchRegions extends AbstractRegionVisitor {
TryCatchBlock prevTB = tryBlocksMap.put(domBlock, tb); TryCatchBlock prevTB = tryBlocksMap.put(domBlock, tb);
if (prevTB != null) { if (prevTB != null) {
LOG.info("!!! TODO: merge try blocks in " + mth); LOG.info("!!! TODO: merge try blocks in {}", mth);
} }
} }
} }
@@ -106,8 +112,7 @@ public class ProcessTryCatchRegions extends AbstractRegionVisitor {
if (region.getSubBlocks().contains(dominator)) { if (region.getSubBlocks().contains(dominator)) {
TryCatchBlock tb = tryBlocksMap.get(dominator); TryCatchBlock tb = tryBlocksMap.get(dominator);
if (!wrapBlocks(region, tb, dominator)) { if (!wrapBlocks(region, tb, dominator)) {
LOG.warn("Can't wrap try/catch for {}, method: {}", dominator, mth); ErrorsCounter.methodError(mth, "Can't wrap try/catch for " + region);
mth.add(AFlag.INCONSISTENT_CODE);
} }
tryBlocksMap.remove(dominator); tryBlocksMap.remove(dominator);
return; return;
@@ -118,34 +123,43 @@ public class ProcessTryCatchRegions extends AbstractRegionVisitor {
/** /**
* Extract all block dominated by 'dominator' to separate region and mark as try/catch block * Extract all block dominated by 'dominator' to separate region and mark as try/catch block
*/ */
private static boolean wrapBlocks(IRegion region, TryCatchBlock tb, BlockNode dominator) { private static boolean wrapBlocks(IRegion replaceRegion, TryCatchBlock tb, BlockNode dominator) {
Region newRegion = new Region(region); IRegion region = replaceRegion;
if (region instanceof LoopRegion) {
LoopRegion loop = (LoopRegion) region;
region = loop.getBody();
}
Region tryRegion = new Region(region);
List<IContainer> subBlocks = region.getSubBlocks(); List<IContainer> subBlocks = region.getSubBlocks();
for (IContainer cont : subBlocks) { for (IContainer cont : subBlocks) {
if (RegionUtils.isDominatedBy(dominator, cont)) { if (RegionUtils.isDominatedBy(dominator, cont)) {
if (isHandlerPath(tb, cont)) { if (isHandlerPath(tb, cont)) {
break; break;
} }
newRegion.getSubBlocks().add(cont); tryRegion.getSubBlocks().add(cont);
} }
} }
if (newRegion.getSubBlocks().isEmpty()) { if (tryRegion.getSubBlocks().isEmpty()) {
return false; return false;
} }
TryCatchRegion tryCatchRegion = new TryCatchRegion(region, tryRegion);
tryRegion.setParent(tryCatchRegion);
tryCatchRegion.setTryCatchBlock(tb.getCatchAttr().getTryBlock());
// replace first node by region // replace first node by region
IContainer firstNode = newRegion.getSubBlocks().get(0); IContainer firstNode = tryRegion.getSubBlocks().get(0);
if (!region.replaceSubBlock(firstNode, newRegion)) { if (!region.replaceSubBlock(firstNode, tryCatchRegion)) {
return false; return false;
} }
subBlocks.removeAll(newRegion.getSubBlocks()); subBlocks.removeAll(tryRegion.getSubBlocks());
newRegion.addAttr(tb.getCatchAttr()); // fix parents for tryRegion sub blocks
for (IContainer cont : tryRegion.getSubBlocks()) {
// fix parents
for (IContainer cont : newRegion.getSubBlocks()) {
if (cont instanceof AbstractRegion) { if (cont instanceof AbstractRegion) {
AbstractRegion aReg = (AbstractRegion) cont; AbstractRegion aReg = (AbstractRegion) cont;
aReg.setParent(newRegion); aReg.setParent(tryRegion);
} }
} }
return true; return true;
@@ -4,6 +4,7 @@ import jadx.core.codegen.NameGen;
import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.DeclareVariablesAttr; import jadx.core.dex.attributes.nodes.DeclareVariablesAttr;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.RegisterArg; import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.args.VarName; import jadx.core.dex.instructions.args.VarName;
@@ -12,8 +13,11 @@ import jadx.core.dex.nodes.IContainer;
import jadx.core.dex.nodes.IRegion; import jadx.core.dex.nodes.IRegion;
import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.regions.IfRegion;
import jadx.core.dex.regions.SwitchRegion; import jadx.core.dex.regions.SwitchRegion;
import jadx.core.dex.regions.conditions.IfRegion;
import jadx.core.dex.regions.loops.ForLoop;
import jadx.core.dex.regions.loops.LoopRegion;
import jadx.core.dex.regions.loops.LoopType;
import jadx.core.dex.visitors.AbstractVisitor; import jadx.core.dex.visitors.AbstractVisitor;
import jadx.core.utils.RegionUtils; import jadx.core.utils.RegionUtils;
import jadx.core.utils.exceptions.JadxException; import jadx.core.utils.exceptions.JadxException;
@@ -111,45 +115,77 @@ public class ProcessVariables extends AbstractVisitor {
} }
} }
private static class CollectUsageRegionVisitor extends TracedRegionVisitor {
private final List<RegisterArg> args;
private final Map<Variable, Usage> usageMap;
public CollectUsageRegionVisitor(Map<Variable, Usage> usageMap) {
this.usageMap = usageMap;
args = new ArrayList<RegisterArg>();
}
@Override
public void processBlockTraced(MethodNode mth, IBlock container, IRegion curRegion) {
regionProcess(mth, curRegion);
int len = container.getInstructions().size();
for (int i = 0; i < len; i++) {
InsnNode insn = container.getInstructions().get(i);
if (insn.contains(AFlag.SKIP)) {
continue;
}
args.clear();
processInsn(insn, curRegion);
}
}
private void regionProcess(MethodNode mth, IRegion region) {
if (region instanceof LoopRegion) {
LoopRegion loopRegion = (LoopRegion) region;
LoopType loopType = loopRegion.getType();
if (loopType instanceof ForLoop) {
ForLoop forLoop = (ForLoop) loopType;
processInsn(forLoop.getInitInsn(), region);
processInsn(forLoop.getIncrInsn(), region);
}
}
}
void processInsn(InsnNode insn, IRegion curRegion) {
if (insn == null) {
return;
}
// result
RegisterArg result = insn.getResult();
if (result != null && result.isRegister()) {
Usage u = addToUsageMap(result, usageMap);
if (u.getArg() == null) {
u.setArg(result);
u.setArgRegion(curRegion);
}
u.getAssigns().add(curRegion);
}
// args
args.clear();
insn.getRegisterArgs(args);
for (RegisterArg arg : args) {
Usage u = addToUsageMap(arg, usageMap);
u.getUseRegions().add(curRegion);
}
}
}
@Override @Override
public void visit(MethodNode mth) throws JadxException { public void visit(MethodNode mth) throws JadxException {
if (mth.isNoCode()) { if (mth.isNoCode()) {
return; return;
} }
final Map<Variable, Usage> usageMap = new LinkedHashMap<Variable, Usage>(); final Map<Variable, Usage> usageMap = new LinkedHashMap<Variable, Usage>();
for (RegisterArg arg : mth.getArguments(true)) { for (RegisterArg arg : mth.getArguments(true)) {
addToUsageMap(arg, usageMap); addToUsageMap(arg, usageMap);
} }
// collect all variables usage // collect all variables usage
IRegionVisitor collect = new TracedRegionVisitor() { IRegionVisitor collect = new CollectUsageRegionVisitor(usageMap);
@Override
public void processBlockTraced(MethodNode mth, IBlock container, IRegion curRegion) {
int len = container.getInstructions().size();
List<RegisterArg> args = new ArrayList<RegisterArg>();
for (int i = 0; i < len; i++) {
InsnNode insn = container.getInstructions().get(i);
// result
RegisterArg result = insn.getResult();
if (result != null && result.isRegister()) {
Usage u = addToUsageMap(result, usageMap);
if (u.getArg() == null) {
u.setArg(result);
u.setArgRegion(curRegion);
}
u.getAssigns().add(curRegion);
}
// args
args.clear();
insn.getRegisterArgs(args);
for (RegisterArg arg : args) {
Usage u = addToUsageMap(arg, usageMap);
u.getUseRegions().add(curRegion);
}
}
}
};
DepthRegionTraversal.traverseAll(mth, collect); DepthRegionTraversal.traverseAll(mth, collect);
// reduce assigns map // reduce assigns map
@@ -158,6 +194,22 @@ public class ProcessVariables extends AbstractVisitor {
usageMap.remove(new Variable(arg)); usageMap.remove(new Variable(arg));
} }
Iterator<Entry<Variable, Usage>> umIt = usageMap.entrySet().iterator();
while (umIt.hasNext()) {
Entry<Variable, Usage> entry = umIt.next();
Usage u = entry.getValue();
// if no assigns => remove
if (u.getAssigns().isEmpty()) {
umIt.remove();
continue;
}
// variable declared at 'catch' clause
InsnNode parentInsn = u.getArg().getParentInsn();
if (parentInsn == null || parentInsn.getType() == InsnType.MOVE_EXCEPTION) {
umIt.remove();
}
}
if (usageMap.isEmpty()) { if (usageMap.isEmpty()) {
return; return;
} }
@@ -168,24 +220,20 @@ public class ProcessVariables extends AbstractVisitor {
for (Iterator<Entry<Variable, Usage>> it = usageMap.entrySet().iterator(); it.hasNext(); ) { for (Iterator<Entry<Variable, Usage>> it = usageMap.entrySet().iterator(); it.hasNext(); ) {
Entry<Variable, Usage> entry = it.next(); Entry<Variable, Usage> entry = it.next();
Usage u = entry.getValue(); Usage u = entry.getValue();
// if no assigns => remove
if (u.getAssigns().isEmpty()) {
it.remove();
continue;
}
// check if variable can be declared at current assigns // check if variable can be declared at current assigns
for (IRegion assignRegion : u.getAssigns()) { for (IRegion assignRegion : u.getAssigns()) {
if (u.getArgRegion() == assignRegion if (u.getArgRegion() == assignRegion
&& canDeclareInRegion(u, assignRegion, regionsOrder)) { && canDeclareInRegion(u, assignRegion, regionsOrder)) {
u.getArg().getParentInsn().add(AFlag.DECLARE_VAR); if (declareAtAssign(u)) {
processVar(u.getArg()); it.remove();
it.remove(); break;
break; }
} }
} }
} }
if (usageMap.isEmpty()) {
return;
}
// apply // apply
for (Entry<Variable, Usage> entry : usageMap.entrySet()) { for (Entry<Variable, Usage> entry : usageMap.entrySet()) {
@@ -226,7 +274,7 @@ public class ProcessVariables extends AbstractVisitor {
} }
} }
Usage addToUsageMap(RegisterArg arg, Map<Variable, Usage> usageMap) { private static Usage addToUsageMap(RegisterArg arg, Map<Variable, Usage> usageMap) {
Variable varId = new Variable(arg); Variable varId = new Variable(arg);
Usage usage = usageMap.get(varId); Usage usage = usageMap.get(varId);
if (usage == null) { if (usage == null) {
@@ -247,6 +295,17 @@ public class ProcessVariables extends AbstractVisitor {
return usage; return usage;
} }
private static boolean declareAtAssign(Usage u) {
RegisterArg arg = u.getArg();
InsnNode parentInsn = arg.getParentInsn();
if (!arg.equals(parentInsn.getResult())) {
return false;
}
parentInsn.add(AFlag.DECLARE_VAR);
processVar(arg);
return true;
}
private static void declareVar(IContainer region, RegisterArg arg) { private static void declareVar(IContainer region, RegisterArg arg) {
DeclareVariablesAttr dv = region.get(AType.DECLARE_VARIABLES); DeclareVariablesAttr dv = region.get(AType.DECLARE_VARIABLES);
if (dv == null) { if (dv == null) {
@@ -262,7 +321,7 @@ public class ProcessVariables extends AbstractVisitor {
} }
private static int calculateOrder(IContainer container, Map<IContainer, Integer> regionsOrder, private static int calculateOrder(IContainer container, Map<IContainer, Integer> regionsOrder,
int id, boolean inc) { int id, boolean inc) {
if (!(container instanceof IRegion)) { if (!(container instanceof IRegion)) {
return id; return id;
} }
@@ -295,12 +354,20 @@ public class ProcessVariables extends AbstractVisitor {
LOG.debug("TODO: Not found order for region {} for {}", region, u); LOG.debug("TODO: Not found order for region {} for {}", region, u);
return false; return false;
} }
// workaround for declare variables used in several loops
if (region instanceof LoopRegion) {
for (IRegion r : u.getAssigns()) {
if (!RegionUtils.isRegionContainsRegion(region, r)) {
return false;
}
}
}
return isAllRegionsAfter(region, pos, u.getAssigns(), regionsOrder) return isAllRegionsAfter(region, pos, u.getAssigns(), regionsOrder)
&& isAllRegionsAfter(region, pos, u.getUseRegions(), regionsOrder); && isAllRegionsAfter(region, pos, u.getUseRegions(), regionsOrder);
} }
private static boolean isAllRegionsAfter(IRegion region, Integer pos, private static boolean isAllRegionsAfter(IRegion region, int pos,
Set<IRegion> regions, Map<IContainer, Integer> regionsOrder) { Set<IRegion> regions, Map<IContainer, Integer> regionsOrder) {
for (IRegion r : regions) { for (IRegion r : regions) {
if (r == region) { if (r == region) {
continue; continue;
@@ -313,7 +380,7 @@ public class ProcessVariables extends AbstractVisitor {
if (pos > rPos) { if (pos > rPos) {
return false; return false;
} }
if (pos.equals(rPos)) { if (pos == rPos) {
return isAllRegionsAfterRecursive(region, regions); return isAllRegionsAfterRecursive(region, regions);
} }
} }
@@ -4,6 +4,7 @@ import jadx.core.Consts;
import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.LoopInfo; import jadx.core.dex.attributes.nodes.LoopInfo;
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
import jadx.core.dex.instructions.IfNode; import jadx.core.dex.instructions.IfNode;
import jadx.core.dex.instructions.InsnType; import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.SwitchNode; import jadx.core.dex.instructions.SwitchNode;
@@ -13,12 +14,12 @@ import jadx.core.dex.nodes.Edge;
import jadx.core.dex.nodes.IRegion; import jadx.core.dex.nodes.IRegion;
import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.regions.IfInfo;
import jadx.core.dex.regions.IfRegion;
import jadx.core.dex.regions.LoopRegion;
import jadx.core.dex.regions.Region; import jadx.core.dex.regions.Region;
import jadx.core.dex.regions.SwitchRegion; import jadx.core.dex.regions.SwitchRegion;
import jadx.core.dex.regions.SynchronizedRegion; import jadx.core.dex.regions.SynchronizedRegion;
import jadx.core.dex.regions.conditions.IfInfo;
import jadx.core.dex.regions.conditions.IfRegion;
import jadx.core.dex.regions.loops.LoopRegion;
import jadx.core.dex.trycatch.ExcHandlerAttr; import jadx.core.dex.trycatch.ExcHandlerAttr;
import jadx.core.dex.trycatch.ExceptionHandler; import jadx.core.dex.trycatch.ExceptionHandler;
import jadx.core.dex.trycatch.TryCatchBlock; import jadx.core.dex.trycatch.TryCatchBlock;
@@ -40,8 +41,10 @@ import java.util.Set;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import static jadx.core.dex.visitors.regions.IfMakerHelper.confirmMerge;
import static jadx.core.dex.visitors.regions.IfMakerHelper.makeIfInfo; import static jadx.core.dex.visitors.regions.IfMakerHelper.makeIfInfo;
import static jadx.core.dex.visitors.regions.IfMakerHelper.mergeNestedIfNodes; import static jadx.core.dex.visitors.regions.IfMakerHelper.mergeNestedIfNodes;
import static jadx.core.dex.visitors.regions.IfMakerHelper.searchNestedIf;
import static jadx.core.utils.BlockUtils.getBlockByOffset; import static jadx.core.utils.BlockUtils.getBlockByOffset;
import static jadx.core.utils.BlockUtils.getNextBlock; import static jadx.core.utils.BlockUtils.getNextBlock;
import static jadx.core.utils.BlockUtils.isPathExists; import static jadx.core.utils.BlockUtils.isPathExists;
@@ -67,7 +70,7 @@ public class RegionMaker {
if (Consts.DEBUG) { if (Consts.DEBUG) {
int id = startBlock.getId(); int id = startBlock.getId();
if (processedBlocks.get(id)) { if (processedBlocks.get(id)) {
LOG.debug(" Block already processed: " + startBlock + ", mth: " + mth); LOG.debug(" Block already processed: {}, mth: {}", startBlock, mth);
} else { } else {
processedBlocks.set(id); processedBlocks.set(id);
} }
@@ -137,9 +140,8 @@ public class RegionMaker {
} }
if (next != null && !stack.containsExit(block) && !stack.containsExit(next)) { if (next != null && !stack.containsExit(block) && !stack.containsExit(next)) {
return next; return next;
} else {
return null;
} }
return null;
} }
private BlockNode processLoop(IRegion curRegion, LoopInfo loop, RegionStack stack) { private BlockNode processLoop(IRegion curRegion, LoopInfo loop, RegionStack stack) {
@@ -163,17 +165,17 @@ public class RegionMaker {
LoopRegion loopRegion = makeLoopRegion(curRegion, loop, exitBlocks); LoopRegion loopRegion = makeLoopRegion(curRegion, loop, exitBlocks);
if (loopRegion == null) { if (loopRegion == null) {
return makeEndlessLoop(curRegion, stack, loop, loopStart); BlockNode exit = makeEndlessLoop(curRegion, stack, loop, loopStart);
insertContinue(loop);
return exit;
} }
curRegion.getSubBlocks().add(loopRegion); curRegion.getSubBlocks().add(loopRegion);
IRegion outerRegion = stack.peekRegion(); IRegion outerRegion = stack.peekRegion();
stack.push(loopRegion); stack.push(loopRegion);
IfInfo info = makeIfInfo(loopRegion.getHeader()); IfInfo condInfo = makeIfInfo(loopRegion.getHeader());
IfInfo condInfo = mergeNestedIfNodes(info); condInfo = searchNestedIf(condInfo);
if (condInfo == null) { confirmMerge(condInfo);
condInfo = info;
}
if (!loop.getLoopBlocks().contains(condInfo.getThenBlock())) { if (!loop.getLoopBlocks().contains(condInfo.getThenBlock())) {
// invert loop condition if 'then' points to exit // invert loop condition if 'then' points to exit
condInfo = IfInfo.invert(condInfo); condInfo = IfInfo.invert(condInfo);
@@ -181,15 +183,15 @@ public class RegionMaker {
loopRegion.setCondition(condInfo.getCondition()); loopRegion.setCondition(condInfo.getCondition());
exitBlocks.removeAll(condInfo.getMergedBlocks()); exitBlocks.removeAll(condInfo.getMergedBlocks());
if (exitBlocks.size() > 0) { if (!exitBlocks.isEmpty()) {
BlockNode loopExit = condInfo.getElseBlock(); BlockNode loopExit = condInfo.getElseBlock();
if (loopExit != null) { if (loopExit != null) {
// add 'break' instruction before path cross between main loop exit and subexit // add 'break' instruction before path cross between main loop exit and sub-exit
for (Edge exitEdge : loop.getExitEdges()) { for (Edge exitEdge : loop.getExitEdges()) {
if (!exitBlocks.contains(exitEdge.getSource())) { if (!exitBlocks.contains(exitEdge.getSource())) {
continue; continue;
} }
tryInsertBreak(stack, loopExit, exitEdge); insertBreak(stack, loopExit, exitEdge);
} }
} }
} }
@@ -232,6 +234,7 @@ public class RegionMaker {
loopRegion.setBody(body); loopRegion.setBody(body);
} }
stack.pop(); stack.pop();
insertContinue(loop);
return out; return out;
} }
@@ -245,7 +248,12 @@ public class RegionMaker {
|| block.getInstructions().get(0).getType() != InsnType.IF) { || block.getInstructions().get(0).getType() != InsnType.IF) {
continue; continue;
} }
LoopRegion loopRegion = new LoopRegion(curRegion, block, block == loop.getEnd()); List<LoopInfo> loops = block.getAll(AType.LOOP);
if (!loops.isEmpty() && loops.get(0) != loop) {
// skip nested loop condition
continue;
}
LoopRegion loopRegion = new LoopRegion(curRegion, loop, block, block == loop.getEnd());
boolean found; boolean found;
if (block == loop.getStart() || block == loop.getEnd() if (block == loop.getStart() || block == loop.getEnd()
|| BlockUtils.isEmptySimplePath(loop.getStart(), block)) { || BlockUtils.isEmptySimplePath(loop.getStart(), block)) {
@@ -257,6 +265,25 @@ public class RegionMaker {
} else { } else {
found = false; found = false;
} }
if (found) {
List<LoopInfo> list = mth.getAllLoopsForBlock(block);
if (list.size() >= 2) {
// bad condition if successors going out of all loops
boolean allOuter = true;
for (BlockNode outerBlock : block.getCleanSuccessors()) {
List<LoopInfo> outLoopList = mth.getAllLoopsForBlock(outerBlock);
outLoopList.remove(loop);
if (!outLoopList.isEmpty()) {
// goes to outer loop
allOuter = false;
break;
}
}
if (allOuter) {
found = false;
}
}
}
if (found) { if (found) {
return loopRegion; return loopRegion;
} }
@@ -266,7 +293,7 @@ public class RegionMaker {
} }
private BlockNode makeEndlessLoop(IRegion curRegion, RegionStack stack, LoopInfo loop, BlockNode loopStart) { private BlockNode makeEndlessLoop(IRegion curRegion, RegionStack stack, LoopInfo loop, BlockNode loopStart) {
LoopRegion loopRegion = new LoopRegion(curRegion, null, false); LoopRegion loopRegion = new LoopRegion(curRegion, loop, null, false);
curRegion.getSubBlocks().add(loopRegion); curRegion.getSubBlocks().add(loopRegion);
loopStart.remove(AType.LOOP); loopStart.remove(AType.LOOP);
@@ -275,16 +302,13 @@ public class RegionMaker {
BlockNode loopExit = null; BlockNode loopExit = null;
// insert 'break' for exits // insert 'break' for exits
List<Edge> exitEdges = loop.getExitEdges(); List<Edge> exitEdges = loop.getExitEdges();
if (exitEdges.size() == 1) { for (Edge exitEdge : exitEdges) {
for (Edge exitEdge : exitEdges) { BlockNode exit = exitEdge.getTarget();
BlockNode exit = exitEdge.getTarget(); if (insertBreak(stack, exit, exitEdge)) {
if (canInsertBreak(exit)) { BlockNode nextBlock = getNextBlock(exit);
exit.getInstructions().add(new InsnNode(InsnType.BREAK, 0)); if (nextBlock != null) {
BlockNode nextBlock = getNextBlock(exit); stack.addExit(nextBlock);
if (nextBlock != null) { loopExit = nextBlock;
stack.addExit(nextBlock);
loopExit = nextBlock;
}
} }
} }
} }
@@ -307,7 +331,8 @@ public class RegionMaker {
} }
private boolean canInsertBreak(BlockNode exit) { private boolean canInsertBreak(BlockNode exit) {
if (exit.contains(AFlag.RETURN)) { if (exit.contains(AFlag.RETURN)
|| BlockUtils.checkLastInsnType(exit, InsnType.BREAK)) {
return false; return false;
} }
List<BlockNode> simplePath = BlockUtils.buildSimplePath(exit); List<BlockNode> simplePath = BlockUtils.buildSimplePath(exit);
@@ -325,21 +350,136 @@ public class RegionMaker {
return true; return true;
} }
private void tryInsertBreak(RegionStack stack, BlockNode loopExit, Edge exitEdge) { private boolean insertBreak(RegionStack stack, BlockNode loopExit, Edge exitEdge) {
BlockNode prev = null;
BlockNode exit = exitEdge.getTarget(); BlockNode exit = exitEdge.getTarget();
while (exit != null) { BlockNode insertBlock = null;
if (prev != null && isPathExists(loopExit, exit)) { boolean confirm = false;
// found cross // process special cases
if (canInsertBreak(exit)) { if (loopExit == exit) {
prev.getInstructions().add(new InsnNode(InsnType.BREAK, 0)); // try/catch at loop end
stack.addExit(exit); BlockNode source = exitEdge.getSource();
if (source.contains(AType.CATCH_BLOCK)
&& source.getSuccessors().size() == 2) {
BlockNode other = BlockUtils.selectOther(loopExit, source.getSuccessors());
if (other != null) {
other = BlockUtils.skipSyntheticSuccessor(other);
if (other.contains(AType.EXC_HANDLER)) {
insertBlock = source;
confirm = true;
}
} }
return;
} }
prev = exit;
exit = getNextBlock(exit);
} }
if (!confirm) {
while (exit != null) {
if (insertBlock != null && isPathExists(loopExit, exit)) {
// found cross
if (canInsertBreak(insertBlock)) {
confirm = true;
break;
}
return false;
}
insertBlock = exit;
List<BlockNode> cs = exit.getCleanSuccessors();
exit = cs.size() == 1 ? cs.get(0) : null;
}
}
if (!confirm) {
return false;
}
InsnNode breakInsn = new InsnNode(InsnType.BREAK, 0);
insertBlock.getInstructions().add(breakInsn);
stack.addExit(exit);
// add label to 'break' if needed
addBreakLabel(exitEdge, exit, breakInsn);
return true;
}
private void addBreakLabel(Edge exitEdge, BlockNode exit, InsnNode breakInsn) {
BlockNode outBlock = BlockUtils.getNextBlock(exitEdge.getTarget());
if (outBlock == null) {
return;
}
List<LoopInfo> exitLoop = mth.getAllLoopsForBlock(outBlock);
if (!exitLoop.isEmpty()) {
return;
}
List<LoopInfo> inLoops = mth.getAllLoopsForBlock(exitEdge.getSource());
if (inLoops.size() < 2) {
return;
}
// search for parent loop
LoopInfo parentLoop = null;
for (LoopInfo loop : inLoops) {
if (loop.getParentLoop() == null) {
parentLoop = loop;
break;
}
}
if (parentLoop == null) {
return;
}
if (parentLoop.getEnd() != exit && !parentLoop.getExitNodes().contains(exit)) {
LoopLabelAttr labelAttr = new LoopLabelAttr(parentLoop);
breakInsn.addAttr(labelAttr);
parentLoop.getStart().addAttr(labelAttr);
}
}
private static void insertContinue(LoopInfo loop) {
BlockNode loopEnd = loop.getEnd();
List<BlockNode> predecessors = loopEnd.getPredecessors();
if (predecessors.size() <= 1) {
return;
}
Set<BlockNode> loopExitNodes = loop.getExitNodes();
for (BlockNode pred : predecessors) {
if (canInsertContinue(pred, predecessors, loopEnd, loopExitNodes)) {
InsnNode cont = new InsnNode(InsnType.CONTINUE, 0);
pred.getInstructions().add(cont);
}
}
}
private static boolean canInsertContinue(BlockNode pred, List<BlockNode> predecessors, BlockNode loopEnd,
Set<BlockNode> loopExitNodes) {
if (!pred.contains(AFlag.SYNTHETIC)
|| BlockUtils.checkLastInsnType(pred, InsnType.CONTINUE)) {
return false;
}
List<BlockNode> preds = pred.getPredecessors();
if (preds.isEmpty()) {
return false;
}
BlockNode codePred = preds.get(0);
if (codePred.contains(AFlag.SKIP)) {
return false;
}
if (loopEnd.isDominator(codePred)
|| loopExitNodes.contains(codePred)) {
return false;
}
if (isDominatedOnBlocks(codePred, predecessors)) {
return false;
}
boolean gotoExit = false;
for (BlockNode exit : loopExitNodes) {
if (BlockUtils.isPathExists(codePred, exit)) {
gotoExit = true;
break;
}
}
return gotoExit;
}
private static boolean isDominatedOnBlocks(BlockNode dom, List<BlockNode> blocks) {
for (BlockNode node : blocks) {
if (!node.isDominator(dom)) {
return false;
}
}
return true;
} }
private final Set<BlockNode> cacheSet = new HashSet<BlockNode>(); private final Set<BlockNode> cacheSet = new HashSet<BlockNode>();
@@ -359,8 +499,7 @@ public class RegionMaker {
BlockNode body = getNextBlock(block); BlockNode body = getNextBlock(block);
if (body == null) { if (body == null) {
mth.add(AFlag.INCONSISTENT_CODE); ErrorsCounter.methodError(mth, "Unexpected end of synchronized block");
LOG.warn("Unexpected end of synchronized block");
return null; return null;
} }
BlockNode exit; BlockNode exit;
@@ -382,7 +521,7 @@ public class RegionMaker {
* Traverse from monitor-enter thru successors and collect blocks contains monitor-exit * Traverse from monitor-enter thru successors and collect blocks contains monitor-exit
*/ */
private static void traverseMonitorExits(SynchronizedRegion region, InsnArg arg, BlockNode block, private static void traverseMonitorExits(SynchronizedRegion region, InsnArg arg, BlockNode block,
Set<BlockNode> exits, Set<BlockNode> visited) { Set<BlockNode> exits, Set<BlockNode> visited) {
visited.add(block); visited.add(block);
for (InsnNode insn : block.getInstructions()) { for (InsnNode insn : block.getInstructions()) {
if (insn.getType() == InsnType.MONITOR_EXIT if (insn.getType() == InsnType.MONITOR_EXIT
@@ -440,9 +579,13 @@ public class RegionMaker {
// invert simple condition (compiler often do it) // invert simple condition (compiler often do it)
currentIf = IfInfo.invert(currentIf); currentIf = IfInfo.invert(currentIf);
} }
currentIf = IfMakerHelper.restructureIf(mth, block, currentIf); IfInfo modifiedIf = IfMakerHelper.restructureIf(mth, block, currentIf);
if (currentIf == null) { if (modifiedIf != null) {
// invalid merged if, check simple one again currentIf = modifiedIf;
} else {
if (currentIf.getMergedBlocks().size() <= 1) {
return null;
}
currentIf = makeIfInfo(block); currentIf = makeIfInfo(block);
currentIf = IfMakerHelper.restructureIf(mth, block, currentIf); currentIf = IfMakerHelper.restructureIf(mth, block, currentIf);
if (currentIf == null) { if (currentIf == null) {
@@ -450,6 +593,7 @@ public class RegionMaker {
return null; return null;
} }
} }
confirmMerge(currentIf);
IfRegion ifRegion = new IfRegion(currentRegion, block); IfRegion ifRegion = new IfRegion(currentRegion, block);
ifRegion.setCondition(currentIf.getCondition()); ifRegion.setCondition(currentIf.getCondition());
@@ -489,7 +633,7 @@ public class RegionMaker {
} }
Map<BlockNode, List<Object>> blocksMap = new LinkedHashMap<BlockNode, List<Object>>(len); Map<BlockNode, List<Object>> blocksMap = new LinkedHashMap<BlockNode, List<Object>>(len);
for (Entry<Integer, List<Object>> entry : casesMap.entrySet()) { for (Map.Entry<Integer, List<Object>> entry : casesMap.entrySet()) {
BlockNode c = getBlockByOffset(entry.getKey(), block.getSuccessors()); BlockNode c = getBlockByOffset(entry.getKey(), block.getSuccessors());
assert c != null; assert c != null;
blocksMap.put(c, entry.getValue()); blocksMap.put(c, entry.getValue());
@@ -626,15 +770,6 @@ public class RegionMaker {
handler.getHandlerRegion().addAttr(excHandlerAttr); handler.getHandlerRegion().addAttr(excHandlerAttr);
} }
static void skipSimplePath(BlockNode block) {
while (block != null
&& block.getCleanSuccessors().size() < 2
&& block.getPredecessors().size() == 1) {
block.add(AFlag.SKIP);
block = getNextBlock(block);
}
}
static boolean isEqualPaths(BlockNode b1, BlockNode b2) { static boolean isEqualPaths(BlockNode b1, BlockNode b2) {
if (b1 == b2) { if (b1 == b2) {
return true; return true;
@@ -4,9 +4,9 @@ import jadx.core.dex.nodes.IContainer;
import jadx.core.dex.nodes.IRegion; import jadx.core.dex.nodes.IRegion;
import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.regions.LoopRegion;
import jadx.core.dex.regions.Region; import jadx.core.dex.regions.Region;
import jadx.core.dex.regions.SynchronizedRegion; import jadx.core.dex.regions.SynchronizedRegion;
import jadx.core.dex.regions.loops.LoopRegion;
import jadx.core.dex.visitors.AbstractVisitor; import jadx.core.dex.visitors.AbstractVisitor;
import jadx.core.utils.InstructionRemover; import jadx.core.utils.InstructionRemover;
import jadx.core.utils.exceptions.JadxException; import jadx.core.utils.exceptions.JadxException;
@@ -8,9 +8,10 @@ import jadx.core.dex.nodes.IContainer;
import jadx.core.dex.nodes.IRegion; import jadx.core.dex.nodes.IRegion;
import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.regions.IfRegion;
import jadx.core.dex.regions.LoopRegion;
import jadx.core.dex.regions.SwitchRegion; import jadx.core.dex.regions.SwitchRegion;
import jadx.core.dex.regions.TryCatchRegion;
import jadx.core.dex.regions.conditions.IfRegion;
import jadx.core.dex.regions.loops.LoopRegion;
import jadx.core.dex.visitors.AbstractVisitor; import jadx.core.dex.visitors.AbstractVisitor;
import jadx.core.utils.exceptions.JadxException; import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.exceptions.JadxRuntimeException; import jadx.core.utils.exceptions.JadxRuntimeException;
@@ -72,7 +73,8 @@ public class ReturnVisitor extends AbstractVisitor {
for (IRegion region : regionStack) { for (IRegion region : regionStack) {
// ignore paths on other branches // ignore paths on other branches
if (region instanceof IfRegion if (region instanceof IfRegion
|| region instanceof SwitchRegion) { || region instanceof SwitchRegion
|| region instanceof TryCatchRegion) {
curContainer = region; curContainer = region;
continue; continue;
} }
@@ -2,56 +2,84 @@ package jadx.core.dex.visitors.regions;
import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.instructions.InsnType; import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.PhiInsn;
import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg; 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.args.RegisterArg;
import jadx.core.dex.instructions.mods.TernaryInsn; import jadx.core.dex.instructions.mods.TernaryInsn;
import jadx.core.dex.nodes.BlockNode; import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.IContainer; import jadx.core.dex.nodes.IContainer;
import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.regions.IfRegion;
import jadx.core.dex.regions.Region; import jadx.core.dex.regions.Region;
import jadx.core.dex.regions.TernaryRegion; import jadx.core.dex.regions.conditions.IfRegion;
import jadx.core.dex.visitors.CodeShrinker; import jadx.core.dex.visitors.CodeShrinker;
import jadx.core.utils.InsnList; import jadx.core.utils.InsnList;
import java.util.HashMap;
import java.util.Map;
public class TernaryMod { public class TernaryMod {
private TernaryMod() { private TernaryMod() {
} }
static void makeTernaryInsn(MethodNode mth, IfRegion ifRegion) { static boolean makeTernaryInsn(MethodNode mth, IfRegion ifRegion) {
if (ifRegion.contains(AFlag.ELSE_IF_CHAIN)) { if (ifRegion.contains(AFlag.ELSE_IF_CHAIN)) {
return; return false;
} }
IContainer thenRegion = ifRegion.getThenRegion(); IContainer thenRegion = ifRegion.getThenRegion();
IContainer elseRegion = ifRegion.getElseRegion(); IContainer elseRegion = ifRegion.getElseRegion();
if (thenRegion == null || elseRegion == null) { if (thenRegion == null || elseRegion == null) {
return; return false;
} }
BlockNode tb = getTernaryInsnBlock(thenRegion); BlockNode tb = getTernaryInsnBlock(thenRegion);
BlockNode eb = getTernaryInsnBlock(elseRegion); BlockNode eb = getTernaryInsnBlock(elseRegion);
if (tb == null || eb == null) { if (tb == null || eb == null) {
return; return false;
} }
BlockNode header = ifRegion.getHeader(); BlockNode header = ifRegion.getHeader();
InsnNode t = tb.getInstructions().get(0); InsnNode t = tb.getInstructions().get(0);
InsnNode e = eb.getInstructions().get(0); InsnNode e = eb.getInstructions().get(0);
if (t.getResult() != null && e.getResult() != null if (t.getSourceLine() != e.getSourceLine()) {
&& t.getResult().equalRegisterAndType(e.getResult()) if (t.getSourceLine() != 0 && e.getSourceLine() != 0) {
&& t.getResult().getSVar().isUsedInPhi()) { // sometimes source lines incorrect
if (!checkLineStats(t, e)) {
return false;
}
} else {
// no debug info
if (containsTernary(t) || containsTernary(e)) {
// don't make nested ternary by default
// TODO: add addition checks
return false;
}
}
}
if (t.getResult() != null && e.getResult() != null) {
PhiInsn phi = t.getResult().getSVar().getUsedInPhi();
if (phi == null || !t.getResult().equalRegisterAndType(e.getResult())) {
return false;
}
if (!ifRegion.getParent().replaceSubBlock(ifRegion, header)) {
return false;
}
InsnList.remove(tb, t); InsnList.remove(tb, t);
InsnList.remove(eb, e); InsnList.remove(eb, e);
RegisterArg resArg = t.getResult().getSVar().getUsedInPhi().getResult(); RegisterArg resArg;
if (phi.getArgsCount() == 2) {
resArg = phi.getResult();
} else {
resArg = t.getResult();
phi.removeArg(e.getResult());
}
TernaryInsn ternInsn = new TernaryInsn(ifRegion.getCondition(), TernaryInsn ternInsn = new TernaryInsn(ifRegion.getCondition(),
resArg, InsnArg.wrapArg(t), InsnArg.wrapArg(e)); resArg, InsnArg.wrapArg(t), InsnArg.wrapArg(e));
ternInsn.setSourceLine(t.getSourceLine()); ternInsn.setSourceLine(t.getSourceLine());
TernaryRegion tern = new TernaryRegion(ifRegion, header);
// TODO: add api for replace regions
ifRegion.setTernRegion(tern);
// remove 'if' instruction // remove 'if' instruction
header.getInstructions().clear(); header.getInstructions().clear();
@@ -59,11 +87,15 @@ public class TernaryMod {
// shrink method again // shrink method again
CodeShrinker.shrinkMethod(mth); CodeShrinker.shrinkMethod(mth);
return; return true;
} }
if (!mth.getReturnType().equals(ArgType.VOID) if (!mth.getReturnType().equals(ArgType.VOID)
&& t.getType() == InsnType.RETURN && e.getType() == InsnType.RETURN) { && t.getType() == InsnType.RETURN && e.getType() == InsnType.RETURN) {
if (!ifRegion.getParent().replaceSubBlock(ifRegion, header)) {
return false;
}
InsnList.remove(tb, t); InsnList.remove(tb, t);
InsnList.remove(eb, e); InsnList.remove(eb, e);
tb.remove(AFlag.RETURN); tb.remove(AFlag.RETURN);
@@ -78,10 +110,10 @@ public class TernaryMod {
header.getInstructions().add(retInsn); header.getInstructions().add(retInsn);
header.add(AFlag.RETURN); header.add(AFlag.RETURN);
ifRegion.setTernRegion(new TernaryRegion(ifRegion, header));
CodeShrinker.shrinkMethod(mth); CodeShrinker.shrinkMethod(mth);
return true;
} }
return false;
} }
private static BlockNode getTernaryInsnBlock(IContainer thenRegion) { private static BlockNode getTernaryInsnBlock(IContainer thenRegion) {
@@ -99,4 +131,59 @@ public class TernaryMod {
} }
return null; return null;
} }
private static boolean containsTernary(InsnNode insn) {
if (insn.getType() == InsnType.TERNARY) {
return true;
}
for (int i = 0; i < insn.getArgsCount(); i++) {
InsnArg arg = insn.getArg(i);
if (arg.isInsnWrap()) {
InsnNode wrapInsn = ((InsnWrapArg) arg).getWrapInsn();
if (containsTernary(wrapInsn)) {
return true;
}
}
}
return false;
}
/**
* Return 'true' if there are several args with same source lines
*/
private static boolean checkLineStats(InsnNode t, InsnNode e) {
if (t.getResult() == null || e.getResult() == null) {
return false;
}
PhiInsn tPhi = t.getResult().getSVar().getUsedInPhi();
PhiInsn ePhi = e.getResult().getSVar().getUsedInPhi();
if (tPhi == null || ePhi == null || tPhi != ePhi) {
return false;
}
Map<Integer, Integer> map = new HashMap<Integer, Integer>(tPhi.getArgsCount());
for (InsnArg arg : tPhi.getArguments()) {
if (!arg.isRegister()) {
continue;
}
InsnNode assignInsn = ((RegisterArg) arg).getAssignInsn();
if (assignInsn == null) {
continue;
}
int sourceLine = assignInsn.getSourceLine();
if (sourceLine != 0) {
Integer count = map.get(sourceLine);
if (count != null) {
map.put(sourceLine, count + 1);
} else {
map.put(sourceLine, 1);
}
}
}
for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
if (entry.getValue() >= 2) {
return true;
}
}
return false;
}
} }
@@ -12,7 +12,12 @@ import jadx.core.utils.exceptions.JadxException;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class EliminatePhiNodes extends AbstractVisitor { public class EliminatePhiNodes extends AbstractVisitor {
private static final Logger LOG = LoggerFactory.getLogger(EliminatePhiNodes.class);
@Override @Override
public void visit(MethodNode mth) throws JadxException { public void visit(MethodNode mth) throws JadxException {
if (mth.isNoCode()) { if (mth.isNoCode()) {
@@ -29,13 +34,20 @@ public class EliminatePhiNodes extends AbstractVisitor {
} }
List<PhiInsn> list = phiList.getList(); List<PhiInsn> list = phiList.getList();
for (PhiInsn phiInsn : list) { for (PhiInsn phiInsn : list) {
for (Iterator<InsnNode> iterator = block.getInstructions().iterator(); iterator.hasNext(); ) { removeInsn(mth, block, phiInsn);
InsnNode insn = iterator.next();
if (insn == phiInsn) {
iterator.remove();
}
}
} }
} }
} }
private static void removeInsn(MethodNode mth, BlockNode block, PhiInsn phiInsn) {
Iterator<InsnNode> it = block.getInstructions().iterator();
while (it.hasNext()) {
InsnNode insn = it.next();
if (insn == phiInsn) {
it.remove();
return;
}
}
LOG.warn("Phi node not removed: {}, mth: {}", phiInsn, mth);
}
} }
@@ -1,5 +1,6 @@
package jadx.core.dex.visitors.ssa; package jadx.core.dex.visitors.ssa;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.PhiListAttr; import jadx.core.dex.attributes.nodes.PhiListAttr;
import jadx.core.dex.instructions.InsnType; import jadx.core.dex.instructions.InsnType;
@@ -40,7 +41,10 @@ public class SSATransform extends AbstractVisitor {
placePhi(mth, i, la); placePhi(mth, i, la);
} }
renameVariables(mth); renameVariables(mth);
removeUselessPhi(mth); fixLastTryCatchAssign(mth);
if (removeUselessPhi(mth)) {
renameVariables(mth);
}
} }
private static void placePhi(MethodNode mth, int regNum, LiveVarAnalysis la) { private static void placePhi(MethodNode mth, int regNum, LiveVarAnalysis la) {
@@ -80,6 +84,7 @@ public class SSATransform extends AbstractVisitor {
} }
PhiInsn phiInsn = new PhiInsn(regNum, block.getPredecessors().size()); PhiInsn phiInsn = new PhiInsn(regNum, block.getPredecessors().size());
phiList.getList().add(phiInsn); phiList.getList().add(phiInsn);
phiInsn.setOffset(block.getStartOffset());
block.getInstructions().add(0, phiInsn); block.getInstructions().add(0, phiInsn);
} }
@@ -126,6 +131,9 @@ public class SSATransform extends AbstractVisitor {
throw new JadxRuntimeException("Can't find predecessor for " + block + " " + s); throw new JadxRuntimeException("Can't find predecessor for " + block + " " + s);
} }
for (PhiInsn phiInsn : phiList.getList()) { for (PhiInsn phiInsn : phiList.getList()) {
if (j >= phiInsn.getArgsCount()) {
continue;
}
int regNum = phiInsn.getResult().getRegNum(); int regNum = phiInsn.getResult().getRegNum();
SSAVar var = vars[regNum]; SSAVar var = vars[regNum];
if (var == null) { if (var == null) {
@@ -143,7 +151,27 @@ public class SSATransform extends AbstractVisitor {
System.arraycopy(inputVars, 0, vars, 0, vars.length); System.arraycopy(inputVars, 0, vars, 0, vars.length);
} }
private static void removeUselessPhi(MethodNode mth) { private static void fixLastTryCatchAssign(MethodNode mth) {
for (BlockNode block : mth.getBasicBlocks()) {
PhiListAttr phiList = block.get(AType.PHI_LIST);
if (phiList == null || !block.contains(AType.EXC_HANDLER)) {
continue;
}
for (PhiInsn phi : phiList.getList()) {
for (int i = 0; i < phi.getArgsCount(); i++) {
RegisterArg arg = phi.getArg(i);
InsnNode parentInsn = arg.getAssignInsn();
if (parentInsn != null
&& parentInsn.getResult() != null
&& parentInsn.contains(AFlag.TRY_LEAVE)) {
phi.removeArg(arg);
}
}
}
}
}
private static boolean removeUselessPhi(MethodNode mth) {
List<PhiInsn> insnToRemove = new ArrayList<PhiInsn>(); List<PhiInsn> insnToRemove = new ArrayList<PhiInsn>();
for (SSAVar var : mth.getSVars()) { for (SSAVar var : mth.getSVars()) {
// phi result not used // phi result not used
@@ -163,7 +191,7 @@ public class SSATransform extends AbstractVisitor {
removePhiWithSameArgs(phi, insnToRemove); removePhiWithSameArgs(phi, insnToRemove);
} }
} }
removePhiList(mth, insnToRemove); return removePhiList(mth, insnToRemove);
} }
private static void removePhiWithSameArgs(PhiInsn phi, List<PhiInsn> insnToRemove) { private static void removePhiWithSameArgs(PhiInsn phi, List<PhiInsn> insnToRemove) {
@@ -190,9 +218,9 @@ public class SSATransform extends AbstractVisitor {
} }
} }
private static void removePhiList(MethodNode mth, List<PhiInsn> insnToRemove) { private static boolean removePhiList(MethodNode mth, List<PhiInsn> insnToRemove) {
if (insnToRemove.isEmpty()) { if (insnToRemove.isEmpty()) {
return; return false;
} }
for (BlockNode block : mth.getBasicBlocks()) { for (BlockNode block : mth.getBasicBlocks()) {
PhiListAttr phiList = block.get(AType.PHI_LIST); PhiListAttr phiList = block.get(AType.PHI_LIST);
@@ -213,5 +241,6 @@ public class SSATransform extends AbstractVisitor {
} }
} }
insnToRemove.clear(); insnToRemove.clear();
return true;
} }
} }
@@ -3,6 +3,7 @@ package jadx.core.dex.visitors.typeinference;
import jadx.core.dex.info.MethodInfo; import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.IndexInsnNode; import jadx.core.dex.instructions.IndexInsnNode;
import jadx.core.dex.instructions.InvokeNode; import jadx.core.dex.instructions.InvokeNode;
import jadx.core.dex.instructions.PhiInsn;
import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg; import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.LiteralArg; import jadx.core.dex.instructions.args.LiteralArg;
@@ -15,6 +16,9 @@ import java.util.List;
public class PostTypeInference { public class PostTypeInference {
private PostTypeInference() {
}
public static boolean process(MethodNode mth, InsnNode insn) { public static boolean process(MethodNode mth, InsnNode insn) {
switch (insn.getType()) { switch (insn.getType()) {
case CONST: case CONST:
@@ -24,7 +28,7 @@ public class PostTypeInference {
long lit = litArg.getLiteral(); long lit = litArg.getLiteral();
if (lit != 0) { if (lit != 0) {
// incorrect literal value for object // incorrect literal value for object
ArgType type = (lit == 1 ? ArgType.BOOLEAN : ArgType.INT); ArgType type = lit == 1 ? ArgType.BOOLEAN : ArgType.INT;
// can't merge with object -> force it // can't merge with object -> force it
litArg.setType(type); litArg.setType(type);
res.getSVar().setType(type); res.getSVar().setType(type);
@@ -84,29 +88,36 @@ public class PostTypeInference {
case CHECK_CAST: { case CHECK_CAST: {
ArgType castType = (ArgType) ((IndexInsnNode) insn).getIndex(); ArgType castType = (ArgType) ((IndexInsnNode) insn).getIndex();
SSAVar sVar = insn.getResult().getSVar(); RegisterArg result = insn.getResult();
// don't override generic types of same base class // don't override generic types of same base class
boolean skip = castType.isObject() && castType.getObject().equals(sVar.getType().getObject()); boolean skip = castType.isObject() && castType.getObject().equals(result.getType().getObject());
if (!skip) { if (!skip) {
// workaround for compiler bug (see TestDuplicateCast) // workaround for compiler bug (see TestDuplicateCast)
sVar.setType(castType); result.getSVar().setType(castType);
} }
return true; return true;
} }
case PHI: {
PhiInsn phi = (PhiInsn) insn;
RegisterArg result = phi.getResult();
SSAVar resultSVar = result.getSVar();
if (resultSVar != null && !result.getType().isTypeKnown()) {
for (InsnArg arg : phi.getArguments()) {
ArgType argType = arg.getType();
if (argType.isTypeKnown()) {
resultSVar.setType(argType);
return true;
}
}
}
return false;
}
default: default:
break; break;
} }
return false; return false;
}
static void setType(InsnArg arg, ArgType type) {
if (arg.isRegister()) {
((RegisterArg) arg).getSVar().setType(type);
} else {
arg.setType(type);
}
} }
private static boolean fixArrayTypes(InsnArg array, InsnArg elem) { private static boolean fixArrayTypes(InsnArg array, InsnArg elem) {
@@ -6,6 +6,9 @@ import jadx.core.dex.nodes.InsnNode;
public class SelectTypeVisitor { public class SelectTypeVisitor {
private SelectTypeVisitor() {
}
public static void visit(InsnNode insn) { public static void visit(InsnNode insn) {
InsnArg res = insn.getResult(); InsnArg res = insn.getResult();
if (res != null && !res.getType().isTypeKnown()) { if (res != null && !res.getType().isTypeKnown()) {
@@ -1,6 +1,5 @@
package jadx.core.dex.visitors.typeinference; package jadx.core.dex.visitors.typeinference;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.instructions.PhiInsn; import jadx.core.dex.instructions.PhiInsn;
import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg; import jadx.core.dex.instructions.args.InsnArg;
@@ -9,7 +8,6 @@ import jadx.core.dex.instructions.args.SSAVar;
import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.visitors.AbstractVisitor; import jadx.core.dex.visitors.AbstractVisitor;
import jadx.core.utils.exceptions.JadxException; import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.exceptions.JadxRuntimeException;
import java.util.List; import java.util.List;
@@ -30,45 +28,36 @@ public class TypeInference extends AbstractVisitor {
// search variable name // search variable name
String name = processVarName(var); String name = processVarName(var);
if (name != null) { var.setName(name);
var.setName(name);
}
} }
// fix type for vars used only in Phi nodes // fix type for vars used only in Phi nodes
for (SSAVar sVar : mth.getSVars()) { for (SSAVar sVar : mth.getSVars()) {
if (sVar.isUsedInPhi()) { PhiInsn phi = sVar.getUsedInPhi();
processPhiNode(sVar.getUsedInPhi()); if (phi != null) {
processPhiNode(phi);
} }
} }
} }
private ArgType processType(SSAVar var) { private static ArgType processType(SSAVar var) {
RegisterArg assign = var.getAssign(); RegisterArg assign = var.getAssign();
List<RegisterArg> useList = var.getUseList(); List<RegisterArg> useList = var.getUseList();
if (assign != null if (useList.isEmpty() || var.isTypeImmutable()) {
&& (useList.isEmpty() || assign.isTypeImmutable())) {
return assign.getType(); return assign.getType();
} }
ArgType type; ArgType type = assign.getType();
if (assign != null) {
type = assign.getType();
} else {
type = ArgType.UNKNOWN;
}
for (RegisterArg arg : useList) { for (RegisterArg arg : useList) {
ArgType useType = arg.getType(); ArgType useType = arg.getType();
if (useType.isTypeKnown()) { ArgType newType = ArgType.merge(type, useType);
type = ArgType.merge(type, useType); if (newType != null) {
} type = newType;
if (arg.getParentInsn().contains(AFlag.INCONSISTENT_CODE)) {
throw new JadxRuntimeException("not removed arg");
} }
} }
return type; return type;
} }
private void processPhiNode(PhiInsn phi) { private static void processPhiNode(PhiInsn phi) {
ArgType type = phi.getResult().getType(); ArgType type = phi.getResult().getType();
if (!type.isTypeKnown()) { if (!type.isTypeKnown()) {
for (InsnArg arg : phi.getArguments()) { for (InsnArg arg : phi.getArguments()) {
@@ -86,20 +75,17 @@ public class TypeInference extends AbstractVisitor {
} }
} }
private String processVarName(SSAVar var) { private static String processVarName(SSAVar var) {
String name = null; String name = var.getAssign().getName();
if (var.getAssign() != null) {
name = var.getAssign().getName();
}
if (name != null) { if (name != null) {
return name; return name;
} }
for (RegisterArg arg : var.getUseList()) { for (RegisterArg arg : var.getUseList()) {
String vName = arg.getName(); String vName = arg.getName();
if (vName != null) { if (vName != null) {
name = vName; return vName;
} }
} }
return name; return null;
} }
} }
@@ -1,10 +1,18 @@
package jadx.core.utils; package jadx.core.utils;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.PhiListAttr;
import jadx.core.dex.instructions.IfNode;
import jadx.core.dex.instructions.InsnType; import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.PhiInsn;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.InsnWrapArg;
import jadx.core.dex.instructions.mods.TernaryInsn;
import jadx.core.dex.nodes.BlockNode; import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.regions.conditions.IfCondition;
import jadx.core.utils.exceptions.JadxRuntimeException; import jadx.core.utils.exceptions.JadxRuntimeException;
import java.util.ArrayList; import java.util.ArrayList;
@@ -113,7 +121,12 @@ public class BlockUtils {
} }
public static BlockNode getBlockByInsn(MethodNode mth, InsnNode insn) { public static BlockNode getBlockByInsn(MethodNode mth, InsnNode insn) {
assert insn != null; if (insn instanceof PhiInsn) {
return searchBlockWithPhi(mth, (PhiInsn) insn);
}
if (insn.contains(AFlag.WRAPPED)) {
return getBlockByWrappedInsn(mth, insn);
}
for (BlockNode bn : mth.getBasicBlocks()) { for (BlockNode bn : mth.getBasicBlocks()) {
if (blockContains(bn, insn)) { if (blockContains(bn, insn)) {
return bn; return bn;
@@ -122,6 +135,87 @@ public class BlockUtils {
return null; return null;
} }
private static BlockNode searchBlockWithPhi(MethodNode mth, PhiInsn insn) {
for (BlockNode block : mth.getBasicBlocks()) {
PhiListAttr phiListAttr = block.get(AType.PHI_LIST);
if (phiListAttr != null) {
for (PhiInsn phiInsn : phiListAttr.getList()) {
if (phiInsn == insn) {
return block;
}
}
}
}
return null;
}
private static BlockNode getBlockByWrappedInsn(MethodNode mth, InsnNode insn) {
for (BlockNode bn : mth.getBasicBlocks()) {
for (InsnNode bi : bn.getInstructions()) {
if (bi == insn || foundWrappedInsn(bi, insn) != null) {
return bn;
}
}
}
return null;
}
public static InsnNode searchInsnParent(MethodNode mth, InsnNode insn) {
InsnArg insnArg = searchWrappedInsnParent(mth, insn);
if (insnArg == null) {
return null;
}
return insnArg.getParentInsn();
}
public static InsnArg searchWrappedInsnParent(MethodNode mth, InsnNode insn) {
if (!insn.contains(AFlag.WRAPPED)) {
return null;
}
for (BlockNode bn : mth.getBasicBlocks()) {
for (InsnNode bi : bn.getInstructions()) {
InsnArg res = foundWrappedInsn(bi, insn);
if (res != null) {
return res;
}
}
}
return null;
}
private static InsnArg foundWrappedInsn(InsnNode container, InsnNode insn) {
for (InsnArg arg : container.getArguments()) {
if (arg.isInsnWrap()) {
InsnNode wrapInsn = ((InsnWrapArg) arg).getWrapInsn();
if (wrapInsn == insn) {
return arg;
}
InsnArg res = foundWrappedInsn(wrapInsn, insn);
if (res != null) {
return res;
}
}
}
if (container instanceof TernaryInsn) {
return foundWrappedInsnInCondition(((TernaryInsn) container).getCondition(), insn);
}
return null;
}
private static InsnArg foundWrappedInsnInCondition(IfCondition cond, InsnNode insn) {
if (cond.isCompare()) {
IfNode cmpInsn = cond.getCompare().getInsn();
return foundWrappedInsn(cmpInsn, insn);
}
for (IfCondition nestedCond : cond.getArgs()) {
InsnArg res = foundWrappedInsnInCondition(nestedCond, insn);
if (res != null) {
return res;
}
}
return null;
}
public static BitSet blocksToBitSet(MethodNode mth, List<BlockNode> blocks) { public static BitSet blocksToBitSet(MethodNode mth, List<BlockNode> blocks) {
BitSet bs = new BitSet(mth.getBasicBlocks().size()); BitSet bs = new BitSet(mth.getBasicBlocks().size());
for (BlockNode block : blocks) { for (BlockNode block : blocks) {
@@ -171,16 +265,16 @@ public class BlockUtils {
Set<BlockNode> set = new HashSet<BlockNode>(); Set<BlockNode> set = new HashSet<BlockNode>();
set.add(start); set.add(start);
if (start != end) { if (start != end) {
addPredcessors(set, end, start); addPredecessors(set, end, start);
} }
return set; return set;
} }
private static void addPredcessors(Set<BlockNode> set, BlockNode from, BlockNode until) { private static void addPredecessors(Set<BlockNode> set, BlockNode from, BlockNode until) {
set.add(from); set.add(from);
for (BlockNode pred : from.getPredecessors()) { for (BlockNode pred : from.getPredecessors()) {
if (pred != until && !set.contains(pred)) { if (pred != until && !set.contains(pred)) {
addPredcessors(set, pred, until); addPredecessors(set, pred, until);
} }
} }
} }
@@ -216,29 +310,6 @@ public class BlockUtils {
return traverseSuccessorsUntil(start, end, new BitSet()); return traverseSuccessorsUntil(start, end, new BitSet());
} }
public static boolean isCleanPathExists(BlockNode start, BlockNode end) {
if (start == end || start.getCleanSuccessors().contains(end)) {
return true;
}
return traverseCleanSuccessorsUntil(start, end, new BitSet());
}
private static boolean traverseCleanSuccessorsUntil(BlockNode from, BlockNode until, BitSet visited) {
for (BlockNode s : from.getCleanSuccessors()) {
if (s == until) {
return true;
}
int id = s.getId();
if (!visited.get(id) && !s.contains(AType.EXC_HANDLER)) {
visited.set(id);
if (traverseSuccessorsUntil(s, until, visited)) {
return true;
}
}
}
return false;
}
public static boolean isOnlyOnePathExists(BlockNode start, BlockNode end) { public static boolean isOnlyOnePathExists(BlockNode start, BlockNode end) {
if (start == end) { if (start == end) {
return true; return true;
@@ -345,4 +416,14 @@ public class BlockUtils {
} }
return block == end; return block == end;
} }
/**
* Return successor of synthetic block or same block otherwise.
*/
public static BlockNode skipSyntheticSuccessor(BlockNode block) {
if (block.isSynthetic() && !block.getSuccessors().isEmpty()) {
return block.getSuccessors().get(0);
}
return block;
}
} }
@@ -2,10 +2,12 @@ package jadx.core.utils;
import java.util.BitSet; import java.util.BitSet;
public class EmptyBitSet extends BitSet { public final class EmptyBitSet extends BitSet {
private static final long serialVersionUID = -1194884945157778639L; private static final long serialVersionUID = -1194884945157778639L;
public static final BitSet EMPTY = new EmptyBitSet();
public EmptyBitSet() { public EmptyBitSet() {
super(0); super(0);
} }
@@ -62,7 +64,7 @@ public class EmptyBitSet extends BitSet {
@Override @Override
public BitSet get(int fromIndex, int toIndex) { public BitSet get(int fromIndex, int toIndex) {
throw new UnsupportedOperationException(); return EMPTY;
} }
@Override @Override
@@ -84,4 +86,9 @@ public class EmptyBitSet extends BitSet {
public void andNot(BitSet set) { public void andNot(BitSet set) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
@Override
public Object clone() {
return this;
}
} }
@@ -40,7 +40,7 @@ public class ErrorsCounter {
if (e.getClass() == JadxOverflowException.class) { if (e.getClass() == JadxOverflowException.class) {
// don't print full stack trace // don't print full stack trace
e = new JadxOverflowException(e.getMessage()); e = new JadxOverflowException(e.getMessage());
LOG.error(msg); LOG.error(msg + ", message: " + e.getMessage());
} else { } else {
LOG.error(msg, e); LOG.error(msg, e);
} }
@@ -57,6 +57,10 @@ public class ErrorsCounter {
return msg; return msg;
} }
public static String classError(ClassNode mth, String errorMsg) {
return classError(mth, errorMsg, null);
}
public static String methodError(MethodNode mth, String errorMsg, Throwable e) { public static String methodError(MethodNode mth, String errorMsg, Throwable e) {
String msg = formatErrorMsg(mth, errorMsg); String msg = formatErrorMsg(mth, errorMsg);
mth.dex().root().getErrorsCounter().addError(mth, msg, e); mth.dex().root().getErrorsCounter().addError(mth, msg, e);
@@ -69,7 +73,7 @@ public class ErrorsCounter {
public void printReport() { public void printReport() {
if (getErrorCount() > 0) { if (getErrorCount() > 0) {
LOG.error(getErrorCount() + " errors occured in following nodes:"); LOG.error("{} errors occurred in following nodes:", getErrorCount());
List<Object> nodes = new ArrayList<Object>(errorNodes); List<Object> nodes = new ArrayList<Object>(errorNodes);
Collections.sort(nodes, new Comparator<Object>() { Collections.sort(nodes, new Comparator<Object>() {
@Override @Override
@@ -79,7 +83,7 @@ public class ErrorsCounter {
}); });
for (Object node : nodes) { for (Object node : nodes) {
String nodeName = node.getClass().getSimpleName().replace("Node", ""); String nodeName = node.getClass().getSimpleName().replace("Node", "");
LOG.error(" " + nodeName + ": " + node); LOG.error(" {}: {}", nodeName, node);
} }
} }
} }
@@ -35,11 +35,7 @@ public class InsnUtils {
} }
public static String insnTypeToString(InsnType type) { public static String insnTypeToString(InsnType type) {
return insnTypeToString(type.toString()); return type.toString() + " ";
}
public static String insnTypeToString(String str) {
return String.format("%s ", str);
} }
public static String indexToString(Object index) { public static String indexToString(Object index) {
@@ -49,7 +45,7 @@ public class InsnUtils {
if (index instanceof String) { if (index instanceof String) {
return "\"" + index + "\""; return "\"" + index + "\"";
} else { } else {
return " " + index; return index.toString();
} }
} }
} }
@@ -1,6 +1,5 @@
package jadx.core.utils; package jadx.core.utils;
import jadx.core.Consts;
import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.instructions.args.InsnArg; import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.InsnWrapArg; import jadx.core.dex.instructions.args.InsnWrapArg;
@@ -66,9 +65,6 @@ public class InstructionRemover {
public static void unbindInsn(MethodNode mth, InsnNode insn) { public static void unbindInsn(MethodNode mth, InsnNode insn) {
RegisterArg r = insn.getResult(); RegisterArg r = insn.getResult();
if (r != null && r.getSVar() != null) { if (r != null && r.getSVar() != null) {
if (Consts.DEBUG && r.getSVar().getUseCount() != 0) {
LOG.debug("Unbind insn with result: {}", insn);
}
mth.removeSVar(r.getSVar()); mth.removeSVar(r.getSVar());
} }
for (InsnArg arg : insn.getArguments()) { for (InsnArg arg : insn.getArguments()) {
@@ -6,6 +6,9 @@ import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.nodes.BlockNode; import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.IContainer; import jadx.core.dex.nodes.IContainer;
import jadx.core.dex.nodes.IRegion; import jadx.core.dex.nodes.IRegion;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.regions.SwitchRegion;
import jadx.core.dex.regions.conditions.IfRegion;
import jadx.core.dex.trycatch.CatchAttr; import jadx.core.dex.trycatch.CatchAttr;
import jadx.core.dex.trycatch.ExceptionHandler; import jadx.core.dex.trycatch.ExceptionHandler;
import jadx.core.dex.trycatch.TryCatchBlock; import jadx.core.dex.trycatch.TryCatchBlock;
@@ -24,7 +27,7 @@ public class RegionUtils {
public static boolean hasExitEdge(IContainer container) { public static boolean hasExitEdge(IContainer container) {
if (container instanceof BlockNode) { if (container instanceof BlockNode) {
BlockNode block = (BlockNode) container; BlockNode block = (BlockNode) container;
return block.getSuccessors().size() != 0 return !block.getSuccessors().isEmpty()
&& !block.contains(AFlag.RETURN); && !block.contains(AFlag.RETURN);
} else if (container instanceof IRegion) { } else if (container instanceof IRegion) {
IRegion region = (IRegion) container; IRegion region = (IRegion) container;
@@ -35,6 +38,29 @@ public class RegionUtils {
} }
} }
public static InsnNode getLastInsn(IContainer container) {
if (container instanceof BlockNode) {
BlockNode block = (BlockNode) container;
List<InsnNode> insnList = block.getInstructions();
if (insnList.isEmpty()) {
return null;
}
return insnList.get(insnList.size() - 1);
} else if (container instanceof IfRegion
|| container instanceof SwitchRegion) {
return null;
} else if (container instanceof IRegion) {
IRegion region = (IRegion) container;
List<IContainer> blocks = region.getSubBlocks();
if (blocks.isEmpty()) {
return null;
}
return getLastInsn(blocks.get(blocks.size() - 1));
} else {
throw new JadxRuntimeException("Unknown container type: " + container.getClass());
}
}
/** /**
* Return true if last block in region has no successors * Return true if last block in region has no successors
*/ */
@@ -83,7 +109,7 @@ public class RegionUtils {
public static boolean notEmpty(IContainer container) { public static boolean notEmpty(IContainer container) {
if (container instanceof BlockNode) { if (container instanceof BlockNode) {
return ((BlockNode) container).getInstructions().size() != 0; return !((BlockNode) container).getInstructions().isEmpty();
} else if (container instanceof IRegion) { } else if (container instanceof IRegion) {
IRegion region = (IRegion) container; IRegion region = (IRegion) container;
for (IContainer block : region.getSubBlocks()) { for (IContainer block : region.getSubBlocks()) {
@@ -1,8 +1,5 @@
package jadx.core.utils; package jadx.core.utils;
import jadx.core.utils.exceptions.JadxRuntimeException;
import java.io.File;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.io.StringWriter; import java.io.StringWriter;
import java.util.Iterator; import java.util.Iterator;
@@ -94,14 +91,7 @@ public class Utils {
return sw.getBuffer().toString(); return sw.getBuffer().toString();
} }
public static void makeDirsForFile(File file) { public static int compare(int x, int y) {
File dir = file.getParentFile(); return (x < y) ? -1 : ((x == y) ? 0 : 1);
if (dir != null && !dir.exists()) {
// if directory already created in other thread mkdirs will return false,
// so check dir existence again
if (!dir.mkdirs() && !dir.exists()) {
throw new JadxRuntimeException("Can't create directory " + dir);
}
}
} }
} }
@@ -1,5 +1,7 @@
package jadx.core.utils.files; package jadx.core.utils.files;
import jadx.core.utils.exceptions.JadxRuntimeException;
import java.io.BufferedInputStream; import java.io.BufferedInputStream;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
@@ -35,4 +37,15 @@ public class FileUtils {
} }
} }
} }
public static void makeDirsForFile(File file) {
File dir = file.getParentFile();
if (dir != null && !dir.exists()) {
// if directory already created in other thread mkdirs will return false,
// so check dir existence again
if (!dir.mkdirs() && !dir.exists()) {
throw new JadxRuntimeException("Can't create directory " + dir);
}
}
}
} }
@@ -66,7 +66,7 @@ public class InputFile {
if (ba.length == 0) { if (ba.length == 0) {
throw new JadxException(j2d.isError() ? j2d.getDxErrors() : "Empty dx output"); throw new JadxException(j2d.isError() ? j2d.getDxErrors() : "Empty dx output");
} else if (j2d.isError()) { } else if (j2d.isError()) {
LOG.warn("dx message: " + j2d.getDxErrors()); LOG.warn("dx message: {}", j2d.getDxErrors());
} }
return new Dex(ba); return new Dex(ba);
} catch (Throwable e) { } catch (Throwable e) {
@@ -1,4 +1,5 @@
package jadx.tests package jadx.tests
import jadx.api.IJadxArgs import jadx.api.IJadxArgs
import jadx.api.JadxDecompiler import jadx.api.JadxDecompiler
import jadx.core.utils.exceptions.JadxException import jadx.core.utils.exceptions.JadxException

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