Compare commits

..

40 Commits

Author SHA1 Message Date
Skylot 671be0af0a add jadx-gui screenshot 2014-08-15 23:08:18 +04:00
Skylot 7e9278f992 don't hardcode maximum Java heap size 2014-08-15 22:39:34 +04:00
Skylot 9194441c47 add ASM to NOTICE file 2014-08-15 22:38:50 +04:00
Skylot 4f307c0085 core: allow subblock replace for 'if' region 2014-08-14 22:38:29 +04:00
Skylot 3bdda55102 core: inline filled array creation 2014-08-14 22:23:13 +04:00
Skylot b657b0fb1f core: fix 'if' processing in 'do/while' loop 2014-08-12 23:00:29 +04:00
Skylot 4935ae6da5 core: hide value parser constants 2014-08-12 22:58:20 +04:00
Skylot 72a50eae43 core: fix missing blocks in loop region 2014-08-11 22:29:10 +04:00
Skylot fa37b90cff core: fix processing try/catch in other catch 2014-08-10 22:36:42 +04:00
Skylot 052a8db606 core: resolve minor issues 2014-08-09 19:32:13 +04:00
Skylot 88ccba166e core: don't inline variables defined in 'try' and used in 'catch' 2014-08-08 22:10:10 +04:00
Skylot 58998089a6 core: redone 'if' structure checking 2014-08-07 22:20:47 +04:00
Skylot f0a73b329e core: fix processing conditions in loop 2014-08-06 22:28:29 +04:00
Skylot c97678a477 refactor: make ErrorsCounter non static 2014-08-05 22:48:31 +04:00
Skylot 2ad739275f core: handle special values for numbers 2014-08-04 22:07:10 +04:00
Skylot caad78885d core: check for duplicated code generation 2014-08-02 16:39:14 +04:00
Skylot a234227b9f core: fix errors in try/catch processing (issue #13) 2014-08-02 16:33:52 +04:00
Skylot 16f736e773 core: fix missing 'catch' code 2014-07-30 23:05:39 +04:00
Skylot 1fe24ad11d travis: cache dependencies 2014-07-29 23:41:10 +04:00
Skylot 33c5e0827a core: always check arguments before inline 2014-07-29 22:59:53 +04:00
Skylot cbd36aeb8f core: fix unused variables declaration 2014-07-29 22:34:18 +04:00
Skylot 2963bb3f41 core: fix issues reported by coverity 2014-07-28 23:19:48 +04:00
Skylot 09a6ceac63 gui: replace underline to color highlight (experimental) 2014-07-28 22:50:55 +04:00
Skylot 75d8a01cab core: improve error reporting 2014-07-28 22:50:55 +04:00
Skylot 0968f75e9a core: fix condition in loops (issue #9) 2014-07-28 22:50:42 +04:00
Skylot bc0db88afa update gradle and dependencies versions 2014-07-18 23:29:36 +04:00
Skylot 5f11f12d0c core: remove redundant spaces for enums 2014-07-18 21:21:24 +04:00
Skylot 2d18950542 core: add some integration tests 2014-07-17 23:32:18 +04:00
Skylot 50d314445a core: fix code style 2014-07-17 23:31:07 +04:00
Skylot f8d57d9265 core: decompile '.class' files 2014-07-15 23:45:25 +04:00
Skylot ebbe6db378 core: fix complex 'if' processing (issues #9 and #12) 2014-07-12 21:26:14 +04:00
skylot 543cad3a23 Merge pull request #11 from Fruiter/master
core: fix nested try-catch blocks processing
2014-07-07 21:13:23 +04:00
fruiter 41cc83dbf6 core: fix nested try-catch blocks processing 2014-07-06 20:15:20 -04:00
Skylot ce7101be88 core: always inline 'this' (issue #10) 2014-06-28 15:39:35 +04:00
Skylot 0a241e3a9c core tests: add custom string matchers 2014-06-28 15:38:50 +04:00
Skylot 37857e88ea core: fix switch statement processing (issue #9 case 2) 2014-06-24 14:08:20 +04:00
Skylot 6fbcf46a8b core: refactor return remover visitor 2014-06-24 14:08:20 +04:00
Skylot a36bc8f29a core: add serial uid to JadxRuntimeException 2014-06-24 14:08:20 +04:00
Skylot 813b7bca6e core: sort error nodes in execution report 2014-06-23 23:37:39 +04:00
Skylot e2945f2a42 core: limit region traversal iterations count 2014-06-23 23:37:39 +04:00
120 changed files with 3161 additions and 847 deletions
+6 -4
View File
@@ -3,17 +3,19 @@ jdk:
- oraclejdk7
- openjdk7
- openjdk6
env:
- TERM=dumb
before_install:
- chmod +x gradlew
script:
- ./gradlew clean build dist
- TERM=dumb ./gradlew clean build dist
after_success:
- ./gradlew jacocoTestReport coveralls
- TERM=dumb ./gradlew jacocoTestReport coveralls
cache:
directories:
- $HOME/.gradle
notifications:
email:
+35
View File
@@ -105,6 +105,41 @@ as published by the Free Software Foundation.
*******************************************************************************
ASM library:
*******************************************************************************
Copyright (c) 2000-2011 INRIA, France Telecom
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holders nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
THE POSSIBILITY OF SUCH DAMAGE.
*******************************************************************************
Jadx-gui components
===================
+12 -1
View File
@@ -9,6 +9,7 @@
Command line and GUI tools for produce Java source code from Android Dex and Apk files
![jadx-gui screenshot](http://skylot.github.io/jadx/jadx-gui.png)
### Downloads
- [unstable](https://drone.io/github.com/skylot/jadx/files)
@@ -38,7 +39,7 @@ Run **jadx** on itself:
### Usage
```
jadx[-gui] [options] <input file> (.dex, .apk or .jar)
jadx[-gui] [options] <input file> (.dex, .apk, .jar or .class)
options:
-d, --output-dir - output directory
-j, --threads-count - processing threads count
@@ -51,6 +52,16 @@ Example:
jadx -d out classes.dex
```
### Troubleshooting
##### Out of memory error:
- Reduce processing threads count (`-j` option)
- Increase maximum java heap size:
* command line (example for linux):
`JAVA_OPTS="-Xmx4G" jadx -j 1 some.apk`
* edit 'jadx' script (jadx.bat on Windows) and setup bigger heap size:
`DEFAULT_JVM_OPTS="-Xmx2500M"`
---------------------------------------
*Licensed under the Apache 2.0 License*
*Copyright 2014 by Skylot*
+2 -2
View File
@@ -56,7 +56,7 @@ buildscript {
dependencies {
// setup coveralls (http://coveralls.io/) see http://github.com/kt3k/coveralls-gradle-plugin
classpath 'org.kt3k.gradle.plugin:coveralls-gradle-plugin:0.4.0'
classpath 'org.kt3k.gradle.plugin:coveralls-gradle-plugin:0.6.1'
}
}
@@ -88,5 +88,5 @@ task clean(type: Delete) {
}
task wrapper(type: Wrapper) {
gradleVersion = '1.12'
gradleVersion = '2.0'
}
Binary file not shown.
+1 -1
View File
@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=http\://services.gradle.org/distributions/gradle-1.12-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-2.0-bin.zip
-10
View File
@@ -9,16 +9,6 @@ dependencies {
compile 'ch.qos.logback:logback-classic:1.1.2'
}
startScripts {
doLast {
// increase default max heap size
String var = 'DEFAULT_JVM_OPTS='
String args = '-Xmx1300M'
unixScript.text = unixScript.text.replace(var + '""', var + '"' + args + '"')
windowsScript.text = windowsScript.text.replace(var, var + args)
}
}
applicationDistribution.with {
into('') {
from '../.'
+12 -17
View File
@@ -1,7 +1,6 @@
package jadx.cli;
import jadx.api.JadxDecompiler;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.exceptions.JadxException;
import java.io.File;
@@ -12,33 +11,29 @@ import org.slf4j.LoggerFactory;
public class JadxCLI {
private static final Logger LOG = LoggerFactory.getLogger(JadxCLI.class);
public static void main(String[] args) {
public static void main(String[] args) throws JadxException {
try {
JadxCLIArgs jadxArgs = new JadxCLIArgs();
if (processArgs(jadxArgs, args)) {
processAndSave(jadxArgs);
}
} catch (JadxException e) {
LOG.error(e.getMessage());
} catch (Throwable e) {
LOG.error("jadx error: " + e.getMessage(), e);
System.exit(1);
}
}
static void processAndSave(JadxCLIArgs jadxArgs) throws JadxException {
try {
JadxDecompiler jadx = new JadxDecompiler(jadxArgs);
jadx.loadFiles(jadxArgs.getInput());
jadx.setOutputDir(jadxArgs.getOutDir());
jadx.save();
} catch (Throwable e) {
throw new JadxException("jadx error: " + e.getMessage(), e);
JadxDecompiler jadx = new JadxDecompiler(jadxArgs);
jadx.setOutputDir(jadxArgs.getOutDir());
jadx.loadFiles(jadxArgs.getInput());
jadx.save();
if (jadx.getErrorsCount() != 0) {
jadx.printErrorsReport();
LOG.error("finished with errors");
} else {
LOG.info("done");
}
if (ErrorsCounter.getErrorCount() != 0) {
ErrorsCounter.printReport();
throw new JadxException("finished with errors");
}
LOG.info("done");
}
static boolean processArgs(JadxCLIArgs jadxArgs, String[] args) throws JadxException {
@@ -1,7 +1,7 @@
package jadx.cli;
import jadx.api.IJadxArgs;
import jadx.core.Consts;
import jadx.api.JadxDecompiler;
import jadx.core.utils.exceptions.JadxException;
import java.io.File;
@@ -20,7 +20,7 @@ import com.beust.jcommander.ParameterException;
public final class JadxCLIArgs implements IJadxArgs {
@Parameter(description = "<input file> (.dex, .apk or .jar)")
@Parameter(description = "<input file> (.dex, .apk, .jar or .class)")
protected List<String> files;
@Parameter(names = {"-d", "--output-dir"}, description = "output directory")
@@ -105,7 +105,7 @@ public final class JadxCLIArgs implements IJadxArgs {
// print usage in not sorted fields order (by default its sorted by description)
PrintStream out = System.out;
out.println();
out.println("jadx - dex to java decompiler, version: " + Consts.JADX_VERSION);
out.println("jadx - dex to java decompiler, version: " + JadxDecompiler.getVersion());
out.println();
out.println("usage: jadx [options] " + jc.getMainParameterDescription());
out.println("options:");
@@ -148,6 +148,7 @@ public final class JadxCLIArgs implements IJadxArgs {
return input;
}
@Override
public File getOutDir() {
return outputDir;
}
+1
View File
@@ -2,6 +2,7 @@ ext.jadxClasspath = 'clsp-data/android-4.3.jar'
dependencies {
compile files('lib/dx-1.8.jar')
compile 'org.ow2.asm:asm:5.0.3'
runtime files(jadxClasspath)
}
@@ -1,7 +1,14 @@
package jadx.api;
import java.io.File;
public class DefaultJadxArgs implements IJadxArgs {
@Override
public File getOutDir() {
return new File("jadx-output");
}
@Override
public int getThreadsCount() {
return Runtime.getRuntime().availableProcessors();
@@ -1,6 +1,10 @@
package jadx.api;
import java.io.File;
public interface IJadxArgs {
File getOutDir();
int getThreadsCount();
boolean isCFGOutput();
@@ -7,7 +7,6 @@ import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.visitors.IDexTreeVisitor;
import jadx.core.dex.visitors.SaveCode;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.exceptions.JadxRuntimeException;
@@ -57,12 +56,13 @@ public final class JadxDecompiler {
private List<JavaClass> classes;
public JadxDecompiler() {
this.args = new DefaultJadxArgs();
init();
this(new DefaultJadxArgs());
}
public JadxDecompiler(IJadxArgs jadxArgs) {
this.args = jadxArgs;
this.outDir = jadxArgs.getOutDir();
reset();
init();
}
@@ -72,17 +72,20 @@ public final class JadxDecompiler {
}
void init() {
reset();
if (outDir == null) {
outDir = new File("jadx-output");
outDir = new DefaultJadxArgs().getOutDir();
}
this.passes = Jadx.getPassesList(args, outDir);
}
void reset() {
ClassInfo.clearCache();
ErrorsCounter.reset();
classes = null;
root = null;
}
public static String getVersion() {
return Jadx.getVersion();
}
public void loadFile(File file) throws JadxException {
@@ -182,7 +185,17 @@ public final class JadxDecompiler {
}
public int getErrorsCount() {
return ErrorsCounter.getErrorCount();
if (root == null) {
return 0;
}
return root.getErrorsCounter().getErrorCount();
}
public void printErrorsReport() {
if (root == null) {
return;
}
root.getErrorsCounter().printReport();
}
void parse() throws DecodeException {
@@ -214,6 +227,6 @@ public final class JadxDecompiler {
@Override
public String toString() {
return "jadx decompiler";
return "jadx decompiler " + getVersion();
}
}
@@ -1,8 +1,6 @@
package jadx.core;
public class Consts {
public static final String JADX_VERSION = Jadx.getVersion();
public static final boolean DEBUG = false;
public static final String CLASS_OBJECT = "java.lang.Object";
+8 -8
View File
@@ -14,6 +14,7 @@ import jadx.core.dex.visitors.IDexTreeVisitor;
import jadx.core.dex.visitors.MethodInlineVisitor;
import jadx.core.dex.visitors.ModVisitor;
import jadx.core.dex.visitors.PrepareForCodeGen;
import jadx.core.dex.visitors.ReSugarCode;
import jadx.core.dex.visitors.SimplifyVisitor;
import jadx.core.dex.visitors.regions.CheckRegions;
import jadx.core.dex.visitors.regions.IfRegionVisitor;
@@ -57,20 +58,23 @@ public class Jadx {
passes.add(new SSATransform());
passes.add(new DebugInfoVisitor());
passes.add(new TypeInference());
passes.add(new ConstInlinerVisitor());
passes.add(new FinishTypeInference());
if (args.isRawCFGOutput()) {
passes.add(new DotGraphVisitor(outDir, false, true));
}
passes.add(new ConstInlinerVisitor());
passes.add(new FinishTypeInference());
passes.add(new EliminatePhiNodes());
passes.add(new ModVisitor());
passes.add(new EnumVisitor());
passes.add(new CodeShrinker());
passes.add(new ReSugarCode());
if (args.isCFGOutput()) {
passes.add(new DotGraphVisitor(outDir, false));
}
passes.add(new RegionMakerVisitor());
passes.add(new IfRegionVisitor());
passes.add(new ReturnVisitor());
@@ -87,10 +91,6 @@ public class Jadx {
passes.add(new MethodInlineVisitor());
passes.add(new ClassModifier());
passes.add(new PrepareForCodeGen());
if (args.isCFGOutput()) {
passes.add(new DotGraphVisitor(outDir, false));
}
}
passes.add(new CodeGen(args));
return passes;
@@ -77,15 +77,15 @@ public class ClspGraph {
return clsName;
}
NClass cls = nameMap.get(implClsName);
if (cls != null) {
if (isImplements(clsName, implClsName)) {
return implClsName;
}
Set<String> anc = getAncestors(clsName);
return searchCommonParent(anc, cls);
if (cls == null) {
LOG.debug("Missing class: {}", implClsName);
return null;
}
LOG.debug("Missing class: {}", implClsName);
return null;
if (isImplements(clsName, implClsName)) {
return implClsName;
}
Set<String> anc = getAncestors(clsName);
return searchCommonParent(anc, cls);
}
private String searchCommonParent(Set<String> anc, NClass cls) {
@@ -93,11 +93,10 @@ public class ClspGraph {
String name = p.getName();
if (anc.contains(name)) {
return name;
} else {
String r = searchCommonParent(anc, p);
if (r != null) {
return r;
}
}
String r = searchCommonParent(anc, p);
if (r != null) {
return r;
}
}
return null;
@@ -109,17 +108,17 @@ public class ClspGraph {
return result;
}
NClass cls = nameMap.get(clsName);
if (cls != null) {
result = new HashSet<String>();
addAncestorsNames(cls, result);
if (result.isEmpty()) {
result = Collections.emptySet();
}
ancestorCache.put(clsName, result);
return result;
if (cls == null) {
LOG.debug("Missing class: {}", clsName);
return Collections.emptySet();
}
LOG.debug("Missing class: {}", clsName);
return Collections.emptySet();
result = new HashSet<String>();
addAncestorsNames(cls, result);
if (result.isEmpty()) {
result = Collections.emptySet();
}
ancestorCache.put(clsName, result);
return result;
}
private void addAncestorsNames(NClass cls, Set<String> result) {
@@ -44,7 +44,7 @@ public class ClassGen {
private final boolean fallback;
private final Set<ClassInfo> imports = new HashSet<ClassInfo>();
private int clsDeclLine = 0;
private int clsDeclLine;
public ClassGen(ClassNode cls, ClassGen parentClsGen, boolean fallback) {
this.cls = cls;
@@ -103,7 +103,9 @@ public class ClassGen {
if (af.isInterface()) {
af = af.remove(AccessFlags.ACC_ABSTRACT);
} else if (af.isEnum()) {
af = af.remove(AccessFlags.ACC_FINAL).remove(AccessFlags.ACC_ABSTRACT);
af = af.remove(AccessFlags.ACC_FINAL)
.remove(AccessFlags.ACC_ABSTRACT)
.remove(AccessFlags.ACC_STATIC);
}
annotationGen.addForClass(clsCode);
@@ -202,35 +204,56 @@ public class ClassGen {
}
private void addInnerClasses(CodeWriter code, ClassNode cls) throws CodegenException {
for (ClassNode innerCls : cls.getInnerClasses()) {
if (innerCls.contains(AFlag.DONT_GENERATE)
|| innerCls.isAnonymous()) {
continue;
}
ClassGen inClGen = new ClassGen(innerCls, getParentGen(), fallback);
code.newLine();
inClGen.addClassCode(code);
imports.addAll(inClGen.getImports());
}
}
private boolean isInnerClassesPresents() {
for (ClassNode innerCls : cls.getInnerClasses()) {
if (!innerCls.isAnonymous()) {
ClassGen inClGen = new ClassGen(innerCls, getParentGen(), fallback);
code.newLine();
inClGen.addClassCode(code);
imports.addAll(inClGen.getImports());
return true;
}
}
return false;
}
private void addMethods(CodeWriter code) {
for (MethodNode mth : cls.getMethods()) {
if (!mth.contains(AFlag.DONT_GENERATE)) {
try {
if (code.getLine() != clsDeclLine) {
code.newLine();
}
addMethod(code, mth);
} catch (Exception e) {
String msg = ErrorsCounter.methodError(mth, "Method generation error", e);
code.startLine("/* " + msg + CodeWriter.NL + Utils.getStackTrace(e) + " */");
}
if (mth.contains(AFlag.DONT_GENERATE)) {
continue;
}
if (code.getLine() != clsDeclLine) {
code.newLine();
}
try {
addMethod(code, mth);
} catch (Exception e) {
String msg = ErrorsCounter.methodError(mth, "Method generation error", e);
code.startLine("/* " + msg + CodeWriter.NL + Utils.getStackTrace(e) + " */");
}
}
}
private boolean isMethodsPresents() {
for (MethodNode mth : cls.getMethods()) {
if (!mth.contains(AFlag.DONT_GENERATE)) {
return true;
}
}
return false;
}
private void addMethod(CodeWriter code, MethodNode mth) throws CodegenException {
MethodGen mthGen = new MethodGen(this, mth);
if (mth.getAccessFlags().isAbstract() || mth.getAccessFlags().isNative()) {
MethodGen mthGen = new MethodGen(this, mth);
mthGen.addDefinition(code);
if (cls.getAccessFlags().isAnnotation()) {
Object def = annotationGen.getAnnotationDefaultValue(mth.getName());
@@ -247,6 +270,12 @@ public class ClassGen {
code.startLine("/* Code decompiled incorrectly, please refer to instructions dump. */");
ErrorsCounter.methodError(mth, "Inconsistent code");
}
MethodGen mthGen;
if (badCode || mth.contains(AType.JADX_ERROR)) {
mthGen = MethodGen.getFallbackMethodGen(mth);
} else {
mthGen = new MethodGen(this, mth);
}
if (mthGen.addDefinition(code)) {
code.add(' ');
}
@@ -284,6 +313,15 @@ public class ClassGen {
}
}
private boolean isFieldsPresents() {
for (FieldNode field : cls.getFields()) {
if (!field.contains(AFlag.DONT_GENERATE)) {
return true;
}
}
return false;
}
private void addEnumFields(CodeWriter code) throws CodegenException {
EnumClassAttr enumFields = cls.get(AType.ENUM_CLASS);
if (enumFields == null) {
@@ -310,21 +348,23 @@ public class ClassGen {
code.add(')');
}
if (f.getCls() != null) {
code.add(' ');
new ClassGen(f.getCls(), this, fallback).addClassBody(code);
}
if (it.hasNext()) {
code.add(',');
}
}
if (enumFields.getFields().isEmpty()) {
code.startLine();
if (isMethodsPresents() || isFieldsPresents() || isInnerClassesPresents()) {
if (enumFields.getFields().isEmpty()) {
code.startLine();
}
code.add(';');
}
code.add(';');
code.newLine();
}
public void useType(CodeWriter code, ArgType type) {
final PrimitiveType stype = type.getPrimitiveType();
PrimitiveType stype = type.getPrimitiveType();
if (stype == null) {
code.add(type.toString());
} else if (stype == PrimitiveType.OBJECT) {
@@ -10,6 +10,7 @@ import jadx.core.dex.instructions.args.LiteralArg;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.regions.Compare;
import jadx.core.dex.regions.IfCondition;
import jadx.core.dex.regions.IfCondition.Mode;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.exceptions.CodegenException;
import jadx.core.utils.exceptions.JadxRuntimeException;
@@ -75,7 +76,7 @@ public class ConditionGen extends InsnGen {
} else if (op == IfOp.NE) {
// != true
code.add('!');
boolean wrap = isWrapNeeded(firstArg);
boolean wrap = isArgWrapNeeded(firstArg);
if (wrap) {
code.add('(');
}
@@ -88,9 +89,9 @@ public class ConditionGen extends InsnGen {
LOG.warn(ErrorsCounter.formatErrorMsg(mth, "Unsupported boolean condition " + op.getSymbol()));
}
addArg(code, firstArg, isWrapNeeded(firstArg));
addArg(code, firstArg, isArgWrapNeeded(firstArg));
code.add(' ').add(op.getSymbol()).add(' ');
addArg(code, secondArg, isWrapNeeded(secondArg));
addArg(code, secondArg, isArgWrapNeeded(secondArg));
}
private void addNot(CodeWriter code, IfCondition condition) throws CodegenException {
@@ -99,7 +100,7 @@ public class ConditionGen extends InsnGen {
}
private void addAndOr(CodeWriter code, IfCondition condition) throws CodegenException {
String mode = condition.getMode() == IfCondition.Mode.AND ? " && " : " || ";
String mode = condition.getMode() == Mode.AND ? " && " : " || ";
Iterator<IfCondition> it = condition.getArgs().iterator();
while (it.hasNext()) {
wrap(code, it.next());
@@ -110,15 +111,16 @@ public class ConditionGen extends InsnGen {
}
private boolean isWrapNeeded(IfCondition condition) {
return !condition.isCompare();
return !condition.isCompare() && condition.getMode() != Mode.NOT;
}
private static boolean isWrapNeeded(InsnArg arg) {
private static boolean isArgWrapNeeded(InsnArg arg) {
if (!arg.isInsnWrap()) {
return false;
}
InsnNode insn = ((InsnWrapArg) arg).getWrapInsn();
if (insn.getType() == InsnType.ARITH) {
InsnType insnType = insn.getType();
if (insnType == InsnType.ARITH) {
switch (((ArithNode) insn).getOp()) {
case ADD:
case SUB:
@@ -127,8 +129,18 @@ public class ConditionGen extends InsnGen {
case REM:
return false;
}
} else if (insn.getType() == InsnType.INVOKE) {
return false;
} else {
switch (insnType) {
case INVOKE:
case SGET:
case IGET:
case AGET:
case CONST:
case ARRAY_LENGTH:
return false;
default:
return true;
}
}
return true;
}
@@ -58,7 +58,7 @@ public class InsnGen {
protected final RootNode root;
protected final boolean fallback;
private static enum Flags {
private enum Flags {
BODY_ONLY,
BODY_ONLY_NOWRAP,
}
@@ -496,7 +496,7 @@ public class InsnGen {
useType(code, insn.getResult().getType());
code.add('{');
for (int i = 0; i < c; i++) {
addArg(code, insn.getArg(i));
addArg(code, insn.getArg(i), false);
if (i + 1 < c) {
code.add(", ");
}
@@ -566,7 +566,7 @@ public class InsnGen {
private void makeConstructor(ConstructorInsn insn, CodeWriter code)
throws CodegenException {
ClassNode cls = mth.dex().resolveClass(insn.getClassType());
if (cls != null && cls.isAnonymous()) {
if (cls != null && cls.isAnonymous() && !fallback) {
// anonymous class construction
ClassInfo parent;
if (cls.getInterfaces().size() == 1) {
@@ -9,7 +9,6 @@ import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.regions.Region;
import jadx.core.dex.trycatch.CatchAttr;
import jadx.core.dex.visitors.DepthTraversal;
import jadx.core.dex.visitors.FallbackModeVisitor;
@@ -132,7 +131,7 @@ public class MethodGen {
if (type.isArray()) {
ArgType elType = type.getArrayElement();
classGen.useType(argsCode, elType);
argsCode.add(" ...");
argsCode.add("...");
} else {
LOG.warn(ErrorsCounter.formatErrorMsg(mth, "Last argument in varargs method not array"));
classGen.useType(argsCode, arg.getType());
@@ -151,49 +150,33 @@ public class MethodGen {
}
public void addInstructions(CodeWriter code) throws CodegenException {
if (mth.contains(AType.JADX_ERROR)) {
code.startLine("throw new UnsupportedOperationException(\"Method not decompiled: ");
code.add(mth.toString());
code.add("\");");
if (mth.contains(AType.JADX_ERROR)
|| mth.contains(AFlag.INCONSISTENT_CODE)
|| mth.getRegion() == null) {
code.startLine("throw new UnsupportedOperationException(\"Method not decompiled: ")
.add(mth.toString())
.add("\");");
JadxErrorAttr err = mth.get(AType.JADX_ERROR);
code.startLine("/* JADX: method processing error */");
Throwable cause = err.getCause();
if (cause != null) {
code.newLine();
code.add("/*");
code.startLine("Error: ").add(Utils.getStackTrace(cause));
code.add("*/");
if (mth.contains(AType.JADX_ERROR)) {
JadxErrorAttr err = mth.get(AType.JADX_ERROR);
code.startLine("/* JADX: method processing error */");
Throwable cause = err.getCause();
if (cause != null) {
code.newLine();
code.add("/*");
code.startLine("Error: ").add(Utils.getStackTrace(cause));
code.add("*/");
}
}
makeMethodDump(code);
} else if (mth.contains(AFlag.INCONSISTENT_CODE)) {
code.startLine("/*");
addFallbackMethodCode(code);
code.startLine("*/");
code.newLine();
} else {
Region startRegion = mth.getRegion();
if (startRegion != null) {
(new RegionGen(this)).makeRegion(code, startRegion);
} else {
addFallbackMethodCode(code);
}
RegionGen regionGen = new RegionGen(this);
regionGen.makeRegion(code, mth.getRegion());
}
}
private void makeMethodDump(CodeWriter code) {
code.startLine("/*");
getFallbackMethodGen(mth).addDefinition(code);
code.add(" {");
code.incIndent();
addFallbackMethodCode(code);
code.decIndent();
code.startLine('}');
code.startLine("*/");
}
public void addFallbackMethodCode(CodeWriter code) {
if (mth.getInstructions() == null) {
// load original instructions
@@ -212,7 +195,7 @@ public class MethodGen {
return;
}
if (mth.getThisArg() != null) {
code.startLine(getFallbackMethodGen(mth).nameGen.useArg(mth.getThisArg())).add(" = this;");
code.startLine(nameGen.useArg(mth.getThisArg())).add(" = this;");
}
addFallbackInsns(code, mth, insnArr, true);
}
@@ -248,7 +231,7 @@ public class MethodGen {
/**
* Return fallback variant of method codegen
*/
private static MethodGen getFallbackMethodGen(MethodNode mth) {
static MethodGen getFallbackMethodGen(MethodNode mth) {
ClassGen clsGen = new ClassGen(mth.getParentClass(), null, true);
return new MethodGen(clsGen, mth);
}
@@ -91,7 +91,7 @@ public class NameGen {
String name = arg.getName();
if (fallback) {
String base = "r" + arg.getRegNum();
if (name != null) {
if (name != null && !name.equals("this")) {
return base + "_" + name;
}
return base;
@@ -172,7 +172,7 @@ public class NameGen {
}
}
public static String getAliasForObject(String name) {
private static String getAliasForObject(String name) {
return OBJ_ALIAS.get(name);
}
@@ -199,7 +199,7 @@ public class RegionGen extends InsnGen {
SwitchNode insn = (SwitchNode) sw.getHeader().getInstructions().get(0);
InsnArg arg = insn.getArg(0);
code.startLine("switch (");
addArg(code, arg);
addArg(code, arg, false);
code.add(") {");
code.incIndent();
@@ -9,7 +9,7 @@ import jadx.core.utils.exceptions.JadxRuntimeException;
public class TypeGen {
public static String signature(ArgType type) {
final PrimitiveType stype = type.getPrimitiveType();
PrimitiveType stype = type.getPrimitiveType();
if (stype == PrimitiveType.OBJECT) {
return Utils.makeQualifiedObjectName(type.getObject());
}
@@ -66,37 +66,91 @@ public class TypeGen {
}
public static String formatShort(short s) {
return "(short) " + wrapNegNum(s < 0, Short.toString(s));
if (s == Short.MAX_VALUE) {
return "Short.MAX_VALUE";
}
if (s == Short.MIN_VALUE) {
return "Short.MIN_VALUE";
}
return "(short) " + Short.toString(s);
}
public static String formatByte(byte b) {
return "(byte) " + wrapNegNum(b < 0, Byte.toString(b));
if (b == Byte.MAX_VALUE) {
return "Byte.MAX_VALUE";
}
if (b == Byte.MIN_VALUE) {
return "Byte.MIN_VALUE";
}
return "(byte) " + Byte.toString(b);
}
public static String formatInteger(int i) {
return wrapNegNum(i < 0, Integer.toString(i));
if (i == Integer.MAX_VALUE) {
return "Integer.MAX_VALUE";
}
if (i == Integer.MIN_VALUE) {
return "Integer.MIN_VALUE";
}
return Integer.toString(i);
}
public static String formatLong(long l) {
if (l == Long.MAX_VALUE) {
return "Long.MAX_VALUE";
}
if (l == Long.MIN_VALUE) {
return "Long.MIN_VALUE";
}
String str = Long.toString(l);
if (Math.abs(l) >= Integer.MAX_VALUE) {
str += "L";
}
return str;
}
public static String formatDouble(double d) {
return wrapNegNum(d < 0, Double.toString(d) + "d");
if (Double.isNaN(d)) {
return "Double.NaN";
}
if (d == Double.NEGATIVE_INFINITY) {
return "Double.NEGATIVE_INFINITY";
}
if (d == Double.POSITIVE_INFINITY) {
return "Double.POSITIVE_INFINITY";
}
if (d == Double.MIN_VALUE) {
return "Double.MIN_VALUE";
}
if (d == Double.MAX_VALUE) {
return "Double.MAX_VALUE";
}
if (d == Double.MIN_NORMAL) {
return "Double.MIN_NORMAL";
}
return Double.toString(d) + "d";
}
public static String formatFloat(float f) {
return wrapNegNum(f < 0, Float.toString(f) + "f");
}
public static String formatLong(long lit) {
String l = Long.toString(lit);
if (lit == Long.MIN_VALUE || Math.abs(lit) >= Integer.MAX_VALUE) {
l += "L";
if (Float.isNaN(f)) {
return "Float.NaN";
}
return wrapNegNum(lit < 0, l);
if (f == Float.NEGATIVE_INFINITY) {
return "Float.NEGATIVE_INFINITY";
}
if (f == Float.POSITIVE_INFINITY) {
return "Float.POSITIVE_INFINITY";
}
if (f == Float.MIN_VALUE) {
return "Float.MIN_VALUE";
}
if (f == Float.MAX_VALUE) {
return "Float.MAX_VALUE";
}
if (f == Float.MIN_NORMAL) {
return "Float.MIN_NORMAL";
}
return Float.toString(f) + "f";
}
private static String wrapNegNum(boolean lz, String str) {
// if (lz)
// return "(" + str + ")";
// else
return str;
}
}
@@ -25,6 +25,9 @@ import jadx.core.dex.trycatch.SplitterBlockAttr;
*/
public class AType<T extends IAttribute> {
private AType() {
}
public static final AType<AttrList<JumpInfo>> JUMP = new AType<AttrList<JumpInfo>>();
public static final AType<AttrList<LoopInfo>> LOOP = new AType<AttrList<LoopInfo>>();
@@ -6,7 +6,7 @@ import java.util.Map;
public class Annotation {
public static enum Visibility {
public enum Visibility {
BUILD, RUNTIME, SYSTEM
}
@@ -53,7 +53,7 @@ public class EnumClassAttr implements IAttribute {
private MethodNode staticMethod;
public EnumClassAttr(int fieldsCount) {
this.fields = new ArrayList<EnumClassAttr.EnumField>(fieldsCount);
this.fields = new ArrayList<EnumField>(fieldsCount);
}
public List<EnumField> getFields() {
@@ -8,7 +8,7 @@ public class AccessInfo {
private final int accFlags;
public static enum AFType {
public enum AFType {
CLASS, FIELD, METHOD
}
@@ -17,7 +17,7 @@ public enum ArithOp {
private final String symbol;
private ArithOp(String symbol) {
ArithOp(String symbol) {
this.symbol = symbol;
}
@@ -12,7 +12,7 @@ public enum IfOp {
private final String symbol;
private IfOp(String symbol) {
IfOp(String symbol) {
this.symbol = symbol;
}
@@ -23,19 +23,19 @@ public enum IfOp {
public IfOp invert() {
switch (this) {
case EQ:
return IfOp.NE;
return NE;
case NE:
return IfOp.EQ;
return EQ;
case LT:
return IfOp.GE;
return GE;
case LE:
return IfOp.GT;
return GT;
case GT:
return IfOp.LE;
return LE;
case GE:
return IfOp.LT;
return LT;
default:
throw new JadxRuntimeException("Unknown if operations type: " + this);
@@ -48,6 +48,10 @@ public abstract class ArgType {
ArgType.clsp = clsp;
}
public static boolean isClspSet() {
return ArgType.clsp != null;
}
private static ArgType primitive(PrimitiveType stype) {
return new PrimitiveArg(stype);
}
@@ -174,7 +178,7 @@ public abstract class ArgType {
private final int bounds;
public WildcardType(ArgType obj, int bound) {
super(ArgType.OBJECT.getObject());
super(OBJECT.getObject());
this.type = obj;
this.bounds = bound;
}
@@ -214,7 +218,7 @@ public abstract class ArgType {
if (bounds == 0) {
return "?";
}
return "? " + (bounds == -1 ? "super" : "extends") + " " + type.toString();
return "? " + (bounds == -1 ? "super" : "extends") + " " + type;
}
}
@@ -398,7 +402,7 @@ public abstract class ArgType {
}
/**
* @see jadx.core.dex.instructions.args.ArgType.WildcardType#getWildcardBounds()
* @see WildcardType#getWildcardBounds()
*/
public int getWildcardBounds() {
return 0;
@@ -513,7 +517,7 @@ public abstract class ArgType {
return OBJECT;
} else {
ArgType res = merge(ea, eb);
return res == null ? null : ArgType.array(res);
return res == null ? null : array(res);
}
} else if (b.equals(OBJECT)) {
return OBJECT;
@@ -2,7 +2,7 @@ package jadx.core.dex.instructions.args;
public class MthParameterArg extends RegisterArg {
private boolean isThis = false;
private boolean isThis;
public MthParameterArg(int rn, ArgType type) {
super(rn, type);
@@ -16,7 +16,7 @@ public enum PrimitiveType {
private final String shortName;
private final String longName;
private PrimitiveType(String shortName, String longName) {
PrimitiveType(String shortName, String longName) {
this.shortName = shortName;
this.longName = longName;
}
@@ -14,7 +14,7 @@ public class ConstructorInsn extends InsnNode {
private final CallType callType;
private final RegisterArg instanceArg;
private static enum CallType {
private enum CallType {
CONSTRUCTOR, // just new instance
SUPER, // super call
THIS, // call constructor from other constructor
@@ -86,6 +86,11 @@ public class BlockNode extends AttrNode implements IBlock {
for (BlockNode b : sucList) {
if (b.contains(AType.EXC_HANDLER)) {
toRemove.add(b);
} else if (b.contains(AFlag.SYNTHETIC)) {
List<BlockNode> s = b.getSuccessors();
if (s.size() == 1 && s.get(0).contains(AType.EXC_HANDLER)) {
toRemove.add(b);
}
}
}
if (block.contains(AFlag.LOOP_END)) {
@@ -259,6 +259,7 @@ public class MethodNode extends LineAttrNode implements ILoadable {
if (ct1 != ct2 && ct2.containsAllHandlers(ct1)) {
for (ExceptionHandler h : ct1.getHandlers()) {
ct2.removeHandler(this, h);
h.setTryBlock(ct1);
}
}
}
@@ -441,6 +442,10 @@ public class MethodNode extends LineAttrNode implements ILoadable {
return exceptionHandlers.isEmpty();
}
public int getExceptionHandlersCount() {
return exceptionHandlers.size();
}
/**
* Return true if exists method with same name and arguments count
*/
@@ -3,6 +3,7 @@ package jadx.core.dex.nodes;
import jadx.core.clsp.ClspGraph;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.files.InputFile;
@@ -14,6 +15,7 @@ import java.util.Map;
public class RootNode {
private final Map<String, ClassNode> names = new HashMap<String, ClassNode>();
private final ErrorsCounter errorsCounter = new ErrorsCounter();
private List<DexNode> dexNodes;
public void load(List<InputFile> dexFiles) throws DecodeException {
@@ -48,11 +50,13 @@ public class RootNode {
}
private static void initClassPath(List<ClassNode> classes) throws IOException, DecodeException {
ClspGraph clsp = new ClspGraph();
clsp.load();
clsp.addApp(classes);
if (!ArgType.isClspSet()) {
ClspGraph clsp = new ClspGraph();
clsp.load();
clsp.addApp(classes);
ArgType.setClsp(clsp);
ArgType.setClsp(clsp);
}
}
private void initInnerClasses(List<ClassNode> classes) {
@@ -99,4 +103,8 @@ public class RootNode {
String fullName = cls.getFullName();
return searchClassByName(fullName);
}
public ErrorsCounter getErrorsCounter() {
return errorsCounter;
}
}
@@ -20,7 +20,7 @@ import com.android.dex.Dex.Section;
public class AnnotationsParser {
private static final Annotation.Visibility[] VISIBILITIES = {
private static final Visibility[] VISIBILITIES = {
Visibility.BUILD,
Visibility.RUNTIME,
Visibility.SYSTEM
@@ -13,22 +13,22 @@ import com.android.dex.Leb128;
public class EncValueParser {
public static final int ENCODED_BYTE = 0x00;
public static final int ENCODED_SHORT = 0x02;
public static final int ENCODED_CHAR = 0x03;
public static final int ENCODED_INT = 0x04;
public static final int ENCODED_LONG = 0x06;
public static final int ENCODED_FLOAT = 0x10;
public static final int ENCODED_DOUBLE = 0x11;
public static final int ENCODED_STRING = 0x17;
public static final int ENCODED_TYPE = 0x18;
public static final int ENCODED_FIELD = 0x19;
public static final int ENCODED_ENUM = 0x1b;
public static final int ENCODED_METHOD = 0x1a;
public static final int ENCODED_ARRAY = 0x1c;
public static final int ENCODED_ANNOTATION = 0x1d;
public static final int ENCODED_NULL = 0x1e;
public static final int ENCODED_BOOLEAN = 0x1f;
private static final int ENCODED_BYTE = 0x00;
private static final int ENCODED_SHORT = 0x02;
private static final int ENCODED_CHAR = 0x03;
private static final int ENCODED_INT = 0x04;
private static final int ENCODED_LONG = 0x06;
private static final int ENCODED_FLOAT = 0x10;
private static final int ENCODED_DOUBLE = 0x11;
private static final int ENCODED_STRING = 0x17;
private static final int ENCODED_TYPE = 0x18;
private static final int ENCODED_FIELD = 0x19;
private static final int ENCODED_ENUM = 0x1b;
private static final int ENCODED_METHOD = 0x1a;
private static final int ENCODED_ARRAY = 0x1c;
private static final int ENCODED_ANNOTATION = 0x1d;
private static final int ENCODED_NULL = 0x1e;
private static final int ENCODED_BOOLEAN = 0x1f;
protected final Section in;
@@ -11,7 +11,7 @@ import org.slf4j.LoggerFactory;
final class LocalVar {
private static final Logger LOG = LoggerFactory.getLogger(LocalVar.class);
private int regNum;
private final int regNum;
private String name;
private ArgType type;
@@ -4,7 +4,11 @@ import jadx.core.dex.attributes.AttrNode;
import jadx.core.dex.nodes.IContainer;
import jadx.core.dex.nodes.IRegion;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public abstract class AbstractRegion extends AttrNode implements IRegion {
private static final Logger LOG = LoggerFactory.getLogger(AbstractRegion.class);
private IRegion parent;
@@ -23,7 +27,7 @@ public abstract class AbstractRegion extends AttrNode implements IRegion {
@Override
public boolean replaceSubBlock(IContainer oldBlock, IContainer newBlock) {
// TODO: implement for others regions
LOG.warn("Replace sub block not supported for class \"{}\"", this.getClass());
return false;
}
}
@@ -20,7 +20,7 @@ import java.util.List;
public final class IfCondition {
public static enum Mode {
public enum Mode {
COMPARE,
NOT,
AND,
@@ -69,13 +69,8 @@ public final class IfCondition {
IfCondition n = new IfCondition(a);
n.addArg(b);
return n;
} else if (b.getMode() == mode) {
IfCondition n = new IfCondition(b);
n.addArg(a);
return n;
} else {
return new IfCondition(mode, Arrays.asList(a, b));
}
return new IfCondition(mode, Arrays.asList(a, b));
}
public Mode getMode() {
@@ -2,41 +2,73 @@ package jadx.core.dex.regions;
import jadx.core.dex.nodes.BlockNode;
import java.util.HashSet;
import java.util.Set;
public final class IfInfo {
IfCondition condition;
BlockNode ifnode;
BlockNode thenBlock;
BlockNode elseBlock;
private final IfCondition condition;
private final Set<BlockNode> mergedBlocks = new HashSet<BlockNode>();
private final BlockNode thenBlock;
private final BlockNode elseBlock;
private BlockNode outBlock;
@Deprecated
private BlockNode ifBlock;
public IfInfo(IfCondition condition, BlockNode thenBlock, BlockNode elseBlock) {
this.condition = condition;
this.thenBlock = thenBlock;
this.elseBlock = elseBlock;
}
public IfInfo(IfCondition condition, IfInfo info) {
this.condition = condition;
this.thenBlock = info.getThenBlock();
this.elseBlock = info.getElseBlock();
this.mergedBlocks.addAll(info.getMergedBlocks());
}
public static IfInfo invert(IfInfo info) {
IfInfo tmpIf = new IfInfo(IfCondition.invert(info.getCondition()),
info.getElseBlock(), info.getThenBlock());
tmpIf.setIfBlock(info.getIfBlock());
tmpIf.getMergedBlocks().addAll(info.getMergedBlocks());
return tmpIf;
}
public IfCondition getCondition() {
return condition;
}
public void setCondition(IfCondition condition) {
this.condition = condition;
}
public BlockNode getIfnode() {
return ifnode;
}
public void setIfnode(BlockNode ifnode) {
this.ifnode = ifnode;
public Set<BlockNode> getMergedBlocks() {
return mergedBlocks;
}
public BlockNode getThenBlock() {
return thenBlock;
}
public void setThenBlock(BlockNode thenBlock) {
this.thenBlock = thenBlock;
}
public BlockNode getElseBlock() {
return elseBlock;
}
public void setElseBlock(BlockNode elseBlock) {
this.elseBlock = elseBlock;
public BlockNode getOutBlock() {
return outBlock;
}
public void setOutBlock(BlockNode outBlock) {
this.outBlock = outBlock;
}
public BlockNode getIfBlock() {
return ifBlock;
}
public void setIfBlock(BlockNode ifBlock) {
this.ifBlock = ifBlock;
}
@Override
public String toString() {
return "IfInfo: " + condition + ", then: " + thenBlock + ", else: " + elseBlock;
}
}
@@ -101,6 +101,19 @@ public final class IfRegion extends AbstractRegion {
return Collections.unmodifiableList(all);
}
@Override
public boolean replaceSubBlock(IContainer oldBlock, IContainer newBlock) {
if (oldBlock == thenRegion) {
thenRegion = newBlock;
return true;
}
if (oldBlock == elseRegion) {
elseRegion = newBlock;
return true;
}
return false;
}
@Override
public String baseString() {
if (ternRegion != null) {
@@ -125,7 +125,9 @@ public final class LoopRegion extends AbstractRegion {
if (conditionBlock != null) {
all.add(conditionBlock);
}
all.add(body);
if (body != null) {
all.add(body);
}
return Collections.unmodifiableList(all);
}
@@ -136,6 +138,6 @@ public final class LoopRegion extends AbstractRegion {
@Override
public String toString() {
return "LOOP";
return "LOOP: " + baseString();
}
}
@@ -331,7 +331,7 @@ public class BlockMakerVisitor extends AbstractVisitor {
private static void markReturnBlocks(MethodNode mth) {
mth.getExitBlocks().clear();
for (BlockNode block : mth.getBasicBlocks()) {
if (BlockUtils.lastInsnType(block, InsnType.RETURN)) {
if (BlockUtils.checkLastInsnType(block, InsnType.RETURN)) {
block.add(AFlag.RETURN);
mth.getExitBlocks().add(block);
}
@@ -399,7 +399,7 @@ public class BlockMakerVisitor extends AbstractVisitor {
if (loops.size() == 1) {
LoopInfo loop = loops.get(0);
List<Edge> edges = loop.getExitEdges();
if (edges.size() > 1) {
if (!edges.isEmpty()) {
boolean change = false;
for (Edge edge : edges) {
BlockNode target = edge.getTarget();
@@ -414,10 +414,7 @@ public class BlockMakerVisitor extends AbstractVisitor {
}
}
}
if (splitReturn(mth)) {
return true;
}
return false;
return splitReturn(mth);
}
private static BlockNode insertBlockBetween(MethodNode mth, BlockNode source, BlockNode target) {
@@ -439,7 +436,6 @@ public class BlockMakerVisitor extends AbstractVisitor {
BlockNode exitBlock = mth.getExitBlocks().get(0);
if (exitBlock.getPredecessors().size() > 1
&& exitBlock.getInstructions().size() == 1
&& !exitBlock.getInstructions().get(0).contains(AType.CATCH_BLOCK)
&& !exitBlock.contains(AFlag.SYNTHETIC)) {
InsnNode returnInsn = exitBlock.getInstructions().get(0);
List<BlockNode> preds = new ArrayList<BlockNode>(exitBlock.getPredecessors());
@@ -87,7 +87,8 @@ public class CodeShrinker extends AbstractVisitor {
}
public WrapInfo checkInline(int assignPos, RegisterArg arg) {
if (assignPos >= inlineBorder || !canMove(assignPos, inlineBorder)) {
if (!arg.isThis()
&& (assignPos >= inlineBorder || !canMove(assignPos, inlineBorder))) {
return null;
}
inlineBorder = assignPos;
@@ -192,7 +193,11 @@ public class CodeShrinker extends AbstractVisitor {
// continue;
// }
SSAVar sVar = arg.getSVar();
if (sVar.getAssign() == null || sVar.getVariableUseCount() != 1) {
if (sVar.getAssign() == null) {
continue;
}
// allow inline only one use arg or 'this'
if (sVar.getVariableUseCount() != 1 && !arg.isThis()) {
continue;
}
InsnNode assignInsn = sVar.getAssign().getParentInsn();
@@ -201,13 +206,9 @@ public class CodeShrinker extends AbstractVisitor {
}
int assignPos = insnList.getIndex(assignInsn);
if (assignPos != -1) {
if (assignInsn.canReorder()) {
wrapList.add(argsInfo.inline(assignPos, arg));
} else {
WrapInfo wrapInfo = argsInfo.checkInline(assignPos, arg);
if (wrapInfo != null) {
wrapList.add(wrapInfo);
}
WrapInfo wrapInfo = argsInfo.checkInline(assignPos, arg);
if (wrapInfo != null) {
wrapList.add(wrapInfo);
}
} else {
// another block
@@ -8,9 +8,11 @@ import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.LiteralArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.args.SSAVar;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.BlockUtils;
import jadx.core.utils.InstructionRemover;
import jadx.core.utils.exceptions.JadxException;
@@ -24,35 +26,52 @@ public class ConstInlinerVisitor extends AbstractVisitor {
if (mth.isNoCode()) {
return;
}
List<InsnNode> toRemove = new ArrayList<InsnNode>();
for (BlockNode block : mth.getBasicBlocks()) {
InstructionRemover remover = new InstructionRemover(mth, block);
toRemove.clear();
for (InsnNode insn : block.getInstructions()) {
if (checkInsn(mth, block, insn)) {
remover.add(insn);
toRemove.add(insn);
}
}
remover.perform();
if (!toRemove.isEmpty()) {
InstructionRemover.removeAll(mth, block, toRemove);
}
}
}
private static boolean checkInsn(MethodNode mth, BlockNode block, InsnNode insn) {
if (insn.getType() == InsnType.CONST) {
InsnArg arg = insn.getArg(0);
if (arg.isLiteral()) {
ArgType resType = insn.getResult().getType();
// make sure arg has correct type
if (!arg.getType().isTypeKnown()) {
arg.merge(resType);
if (insn.getType() != InsnType.CONST) {
return false;
}
InsnArg arg = insn.getArg(0);
if (!arg.isLiteral()) {
return false;
}
SSAVar sVar = insn.getResult().getSVar();
if (mth.getExceptionHandlersCount() != 0) {
for (RegisterArg useArg : sVar.getUseList()) {
InsnNode parentInsn = useArg.getParentInsn();
if (parentInsn != null) {
// TODO: speed up expensive operations
BlockNode useBlock = BlockUtils.getBlockByInsn(mth, parentInsn);
if (!BlockUtils.isCleanPathExists(block, useBlock)) {
return false;
}
}
long lit = ((LiteralArg) arg).getLiteral();
return replaceConst(mth, insn, lit);
}
}
return false;
ArgType resType = insn.getResult().getType();
// make sure arg has correct type
if (!arg.getType().isTypeKnown()) {
arg.merge(resType);
}
long lit = ((LiteralArg) arg).getLiteral();
return replaceConst(mth, sVar, lit);
}
private static boolean replaceConst(MethodNode mth, InsnNode insn, long literal) {
List<RegisterArg> use = new ArrayList<RegisterArg>(insn.getResult().getSVar().getUseList());
private static boolean replaceConst(MethodNode mth, SSAVar sVar, long literal) {
List<RegisterArg> use = new ArrayList<RegisterArg>(sVar.getUseList());
int replaceCount = 0;
for (RegisterArg arg : use) {
// if (arg.getSVar().isUsedInPhi()) {
@@ -80,7 +99,7 @@ public class ConstInlinerVisitor extends AbstractVisitor {
}
/**
* This is method similar to PostTypeInference.visit method,
* This is method similar to PostTypeInference.process method,
* but contains some expensive operations needed only after constant inline
*/
private static void fixTypes(MethodNode mth, InsnNode insn, LiteralArg litArg) {
@@ -0,0 +1,78 @@
package jadx.core.dex.visitors;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.LiteralArg;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.InstructionRemover;
import jadx.core.utils.exceptions.JadxException;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ReSugarCode extends AbstractVisitor {
private static final Logger LOG = LoggerFactory.getLogger(ReSugarCode.class);
@Override
public void visit(MethodNode mth) throws JadxException {
if (mth.isNoCode()) {
return;
}
InstructionRemover remover = new InstructionRemover(mth);
for (BlockNode block : mth.getBasicBlocks()) {
remover.setBlock(block);
List<InsnNode> instructions = block.getInstructions();
int size = instructions.size();
for (int i = 0; i < size; i++) {
InsnNode replacedInsn = process(mth, instructions, i, remover);
if (replacedInsn != null) {
instructions.set(i, replacedInsn);
}
}
remover.perform();
}
}
private static InsnNode process(MethodNode mth, List<InsnNode> instructions, int i, InstructionRemover remover) {
InsnNode insn = instructions.get(i);
switch (insn.getType()) {
case NEW_ARRAY:
return processNewArray(mth, instructions, i, remover);
default:
return null;
}
}
/**
* Replace new array and sequence of array-put to new filled-array instruction.
*/
private static InsnNode processNewArray(MethodNode mth, List<InsnNode> instructions, int i, InstructionRemover remover) {
InsnNode insn = instructions.get(i);
InsnArg arg = insn.getArg(0);
if (!arg.isLiteral()) {
return null;
}
int len = (int) ((LiteralArg) arg).getLiteral();
int size = instructions.size();
if (len <= 0 || i + len >= size || instructions.get(i + len).getType() != InsnType.APUT) {
return null;
}
InsnNode filledArr = new InsnNode(InsnType.FILLED_NEW_ARRAY, len);
filledArr.setResult(insn.getResult());
for (int j = 0; j < len; j++) {
InsnNode put = instructions.get(i + 1 + j);
if (put.getType() != InsnType.APUT) {
LOG.debug("Not a APUT in expected new filled array: {}, method: {}", put, mth);
return null;
}
filledArr.addArg(put.getArg(2));
remover.add(put);
}
return filledArr;
}
}
@@ -11,7 +11,9 @@ import jadx.core.dex.visitors.AbstractVisitor;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.exceptions.JadxException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.slf4j.Logger;
@@ -30,16 +32,27 @@ public class CheckRegions extends AbstractVisitor {
// check if all blocks included in regions
final Set<BlockNode> blocksInRegions = new HashSet<BlockNode>();
IRegionVisitor collectBlocks = new AbstractRegionVisitor() {
DepthRegionTraversal.traverseAll(mth, new AbstractRegionVisitor() {
@Override
public void processBlock(MethodNode mth, IBlock container) {
if (container instanceof BlockNode) {
blocksInRegions.add((BlockNode) container);
if (!(container instanceof BlockNode)) {
return;
}
BlockNode block = (BlockNode) container;
if (blocksInRegions.add(block)) {
return;
}
if (!block.contains(AFlag.RETURN)
&& !block.contains(AFlag.SKIP)
&& !block.contains(AFlag.SYNTHETIC)
&& !block.getInstructions().isEmpty()) {
// TODO
// mth.add(AFlag.INCONSISTENT_CODE);
LOG.debug(" Duplicated block: {} in {}", block, mth);
// printRegionsWithBlock(mth, block);
}
}
};
DepthRegionTraversal.traverseAll(mth, collectBlocks);
});
if (mth.getBasicBlocks().size() != blocksInRegions.size()) {
for (BlockNode block : mth.getBasicBlocks()) {
if (!blocksInRegions.contains(block)
@@ -52,19 +65,30 @@ public class CheckRegions extends AbstractVisitor {
}
// check loop conditions
IRegionVisitor checkLoops = new AbstractRegionVisitor() {
DepthRegionTraversal.traverseAll(mth, new AbstractRegionVisitor() {
@Override
public void enterRegion(MethodNode mth, IRegion region) {
if (region instanceof LoopRegion) {
LoopRegion loop = (LoopRegion) region;
BlockNode loopHeader = loop.getHeader();
BlockNode loopHeader = ((LoopRegion) region).getHeader();
if (loopHeader != null && loopHeader.getInstructions().size() != 1) {
ErrorsCounter.methodError(mth, "Incorrect condition in loop: " + loopHeader);
mth.add(AFlag.INCONSISTENT_CODE);
}
}
}
};
DepthRegionTraversal.traverseAll(mth, checkLoops);
});
}
private static void printRegionsWithBlock(MethodNode mth, final BlockNode block) {
final List<IRegion> regions = new ArrayList<IRegion>();
DepthRegionTraversal.traverseAll(mth, new TracedRegionVisitor() {
@Override
public void processBlockTraced(MethodNode mth, IBlock container, IRegion currentRegion) {
if (block.equals(container)) {
regions.add(currentRegion);
}
}
});
LOG.debug(" Found block: {} in regions: {}", block, regions);
}
}
@@ -5,9 +5,12 @@ import jadx.core.dex.nodes.IContainer;
import jadx.core.dex.nodes.IRegion;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.trycatch.ExceptionHandler;
import jadx.core.utils.exceptions.JadxOverflowException;
public class DepthRegionTraversal {
private static final int ITERATIVE_LIMIT = 500;
private DepthRegionTraversal() {
}
@@ -24,8 +27,12 @@ public class DepthRegionTraversal {
public static void traverseAllIterative(MethodNode mth, IRegionIterativeVisitor visitor) {
boolean repeat;
int k = 0;
do {
repeat = traverseAllIterativeInternal(mth, visitor);
if (k++ > ITERATIVE_LIMIT) {
throw new JadxOverflowException("Iterative traversal limit reached, method: " + mth);
}
} while (repeat);
}
@@ -0,0 +1,253 @@
package jadx.core.dex.visitors.regions;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.instructions.IfNode;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.regions.IfCondition;
import jadx.core.dex.regions.IfCondition.Mode;
import jadx.core.dex.regions.IfInfo;
import jadx.core.utils.BlockUtils;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static jadx.core.utils.BlockUtils.isPathExists;
public class IfMakerHelper {
private static final Logger LOG = LoggerFactory.getLogger(IfMakerHelper.class);
private IfMakerHelper() {
}
static IfInfo makeIfInfo(BlockNode ifBlock) {
IfNode ifNode = (IfNode) ifBlock.getInstructions().get(0);
IfCondition condition = IfCondition.fromIfNode(ifNode);
IfInfo info = new IfInfo(condition, ifNode.getThenBlock(), ifNode.getElseBlock());
info.setIfBlock(ifBlock);
info.getMergedBlocks().add(ifBlock);
return info;
}
static IfInfo restructureIf(MethodNode mth, BlockNode block, IfInfo info) {
final BlockNode thenBlock = info.getThenBlock();
final BlockNode elseBlock = info.getElseBlock();
// select 'then', 'else' and 'exit' blocks
if (thenBlock.contains(AFlag.RETURN) && elseBlock.contains(AFlag.RETURN)) {
info.setOutBlock(null);
return info;
}
boolean badThen = !allPathsFromIf(thenBlock, info);
boolean badElse = !allPathsFromIf(elseBlock, info);
if (badThen && badElse) {
LOG.debug("Stop processing blocks after 'if': {}, method: {}", info, mth);
return null;
}
if (badElse) {
info = new IfInfo(info.getCondition(), thenBlock, null);
info.setOutBlock(elseBlock);
} else if (badThen) {
info = IfInfo.invert(info);
info = new IfInfo(info.getCondition(), elseBlock, null);
info.setOutBlock(thenBlock);
} else {
List<BlockNode> thenSC = thenBlock.getCleanSuccessors();
List<BlockNode> elseSC = elseBlock.getCleanSuccessors();
if (thenSC.size() == 1 && sameElements(thenSC, elseSC)) {
info.setOutBlock(thenSC.get(0));
} else if (info.getMergedBlocks().size() == 1
&& block.getDominatesOn().size() == 2) {
info.setOutBlock(BlockUtils.getPathCross(mth, thenBlock, elseBlock));
}
}
if (info.getOutBlock() == null) {
for (BlockNode d : block.getDominatesOn()) {
if (d != thenBlock && d != elseBlock
&& !info.getMergedBlocks().contains(d)
&& isPathExists(thenBlock, d)) {
info.setOutBlock(d);
break;
}
}
}
if (BlockUtils.isBackEdge(block, info.getOutBlock())) {
info.setOutBlock(null);
}
return info;
}
private static boolean allPathsFromIf(BlockNode block, IfInfo info) {
List<BlockNode> preds = block.getPredecessors();
Set<BlockNode> ifBlocks = info.getMergedBlocks();
for (BlockNode pred : preds) {
if (!ifBlocks.contains(pred) && !pred.contains(AFlag.LOOP_END)) {
return false;
}
}
return true;
}
private static boolean sameElements(Collection<BlockNode> c1, Collection<BlockNode> c2) {
return c1.size() == c2.size() && c1.containsAll(c2);
}
static IfInfo mergeNestedIfNodes(IfInfo currentIf) {
BlockNode curThen = currentIf.getThenBlock();
BlockNode curElse = currentIf.getElseBlock();
if (curThen == curElse) {
return null;
}
boolean followThenBranch;
IfInfo nextIf = getNextIf(currentIf, curThen);
if (nextIf != null) {
followThenBranch = true;
} else {
nextIf = getNextIf(currentIf, curElse);
if (nextIf != null) {
followThenBranch = false;
} else {
return null;
}
}
if (isInversionNeeded(currentIf, nextIf)) {
// invert current node for match pattern
nextIf = IfInfo.invert(nextIf);
}
if (!RegionMaker.isEqualPaths(curElse, nextIf.getElseBlock())
&& !RegionMaker.isEqualPaths(curThen, nextIf.getThenBlock())) {
// complex condition, run additional checks
if (checkConditionBranches(curThen, curElse) || checkConditionBranches(curElse, curThen)) {
return null;
}
BlockNode otherBranchBlock = followThenBranch ? curElse : curThen;
if (!isPathExists(nextIf.getIfBlock(), otherBranchBlock)) {
return null;
}
if (isPathExists(nextIf.getThenBlock(), otherBranchBlock)
&& isPathExists(nextIf.getElseBlock(), otherBranchBlock)) {
// both branches paths points to one block
return null;
}
// this is nested conditions with different mode (i.e (a && b) || c),
// search next condition for merge, get null if failed
IfInfo tmpIf = mergeNestedIfNodes(nextIf);
if (tmpIf != null) {
nextIf = tmpIf;
if (isInversionNeeded(currentIf, nextIf)) {
nextIf = IfInfo.invert(nextIf);
}
}
}
IfInfo result = mergeIfInfo(currentIf, nextIf, followThenBranch);
// search next nested if block
IfInfo next = mergeNestedIfNodes(result);
if (next != null) {
return next;
}
return result;
}
private static boolean isInversionNeeded(IfInfo currentIf, IfInfo nextIf) {
return RegionMaker.isEqualPaths(currentIf.getElseBlock(), nextIf.getThenBlock())
|| RegionMaker.isEqualPaths(currentIf.getThenBlock(), nextIf.getElseBlock());
}
private static boolean checkConditionBranches(BlockNode from, BlockNode to) {
return from.getCleanSuccessors().size() == 1 && from.getCleanSuccessors().contains(to);
}
private static IfInfo mergeIfInfo(IfInfo first, IfInfo second, boolean followThenBranch) {
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());
IfInfo result = new IfInfo(condition, second);
result.setIfBlock(first.getIfBlock());
result.getMergedBlocks().addAll(first.getMergedBlocks());
result.getMergedBlocks().addAll(second.getMergedBlocks());
return result;
}
private static IfInfo getNextIf(IfInfo info, BlockNode block) {
if (!canSelectNext(info, block)) {
return null;
}
BlockNode nestedIfBlock = getNextIfNode(block);
if (nestedIfBlock != null) {
return makeIfInfo(nestedIfBlock);
}
return null;
}
private static boolean canSelectNext(IfInfo info, BlockNode block) {
if (block.getPredecessors().size() == 1) {
return true;
}
if (info.getMergedBlocks().containsAll(block.getPredecessors())) {
return true;
}
return false;
}
private static BlockNode getNextIfNode(BlockNode block) {
if (block == null || block.contains(AType.LOOP) || block.contains(AFlag.SKIP)) {
return null;
}
List<InsnNode> insns = block.getInstructions();
if (insns.size() == 1 && insns.get(0).getType() == InsnType.IF) {
return block;
}
// skip this block and search in successors chain
List<BlockNode> successors = block.getSuccessors();
if (successors.size() != 1) {
return null;
}
BlockNode next = successors.get(0);
if (next.getPredecessors().size() != 1) {
return null;
}
boolean pass = true;
if (!insns.isEmpty()) {
// check that all instructions can be inlined
for (InsnNode insn : insns) {
RegisterArg res = insn.getResult();
if (res == null) {
pass = false;
break;
}
List<RegisterArg> useList = res.getSVar().getUseList();
if (useList.size() != 1) {
pass = false;
break;
}
InsnArg arg = useList.get(0);
InsnNode usePlace = arg.getParentInsn();
if (!BlockUtils.blockContains(block, usePlace)
&& !BlockUtils.blockContains(next, usePlace)) {
pass = false;
break;
}
}
}
if (pass) {
return getNextIfNode(next);
}
return null;
}
}
@@ -7,6 +7,7 @@ import jadx.core.dex.nodes.IContainer;
import jadx.core.dex.nodes.IRegion;
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.visitors.AbstractVisitor;
@@ -14,6 +15,8 @@ import jadx.core.utils.RegionUtils;
import java.util.List;
import static jadx.core.utils.RegionUtils.insnsCount;
public class IfRegionVisitor extends AbstractVisitor implements IRegionVisitor, IRegionIterativeVisitor {
@Override
@@ -48,6 +51,7 @@ public class IfRegionVisitor extends AbstractVisitor implements IRegionVisitor,
private static void processIfRegion(MethodNode mth, IfRegion ifRegion) {
simplifyIfCondition(ifRegion);
moveReturnToThenBlock(mth, ifRegion);
moveBreakToThenBlock(ifRegion);
markElseIfChains(ifRegion);
TernaryMod.makeTernaryInsn(mth, ifRegion);
@@ -56,14 +60,40 @@ public class IfRegionVisitor extends AbstractVisitor implements IRegionVisitor,
private static void simplifyIfCondition(IfRegion ifRegion) {
if (ifRegion.simplifyCondition()) {
IfCondition condition = ifRegion.getCondition();
if (condition.getMode() == IfCondition.Mode.NOT) {
if (condition.getMode() == Mode.NOT) {
invertIfRegion(ifRegion);
}
}
if (RegionUtils.isEmpty(ifRegion.getThenRegion())
|| hasSimpleReturnBlock(ifRegion.getThenRegion())) {
IContainer elseRegion = ifRegion.getElseRegion();
if (elseRegion == null || RegionUtils.isEmpty(elseRegion)) {
return;
}
boolean thenIsEmpty = RegionUtils.isEmpty(ifRegion.getThenRegion());
if (thenIsEmpty || hasSimpleReturnBlock(ifRegion.getThenRegion())) {
invertIfRegion(ifRegion);
}
if (!thenIsEmpty) {
// move 'if' from then to make 'else if' chain
if (isIfRegion(ifRegion.getThenRegion())
&& !isIfRegion(elseRegion)) {
invertIfRegion(ifRegion);
}
}
}
private static boolean isIfRegion(IContainer container) {
if (container instanceof IfRegion) {
return true;
}
if (container instanceof IRegion) {
List<IContainer> subBlocks = ((IRegion) container).getSubBlocks();
if (subBlocks.size() == 1 && subBlocks.get(0) instanceof IfRegion) {
return true;
}
}
return false;
}
private static void moveReturnToThenBlock(MethodNode mth, IfRegion ifRegion) {
@@ -74,6 +104,13 @@ public class IfRegionVisitor extends AbstractVisitor implements IRegionVisitor,
}
}
private static void moveBreakToThenBlock(IfRegion ifRegion) {
if (ifRegion.getElseRegion() != null
&& RegionUtils.hasBreakInsn(ifRegion.getElseRegion())) {
invertIfRegion(ifRegion);
}
}
/**
* Mark if-else-if chains
*/
@@ -95,7 +132,8 @@ public class IfRegionVisitor extends AbstractVisitor implements IRegionVisitor,
if (ifRegion.getElseRegion() != null
&& !ifRegion.contains(AFlag.ELSE_IF_CHAIN)
&& !ifRegion.getElseRegion().contains(AFlag.ELSE_IF_CHAIN)
&& RegionUtils.hasExitBlock(ifRegion.getThenRegion())) {
&& hasBranchTerminator(ifRegion)
&& insnsCount(ifRegion.getThenRegion()) < 2) {
IRegion parent = ifRegion.getParent();
Region newRegion = new Region(parent);
if (parent.replaceSubBlock(ifRegion, newRegion)) {
@@ -108,6 +146,12 @@ public class IfRegionVisitor extends AbstractVisitor implements IRegionVisitor,
return false;
}
private static boolean hasBranchTerminator(IfRegion ifRegion) {
// TODO: check for exception throw
return RegionUtils.hasExitBlock(ifRegion.getThenRegion())
|| RegionUtils.hasBreakInsn(ifRegion.getThenRegion());
}
private static void invertIfRegion(IfRegion ifRegion) {
IContainer elseRegion = ifRegion.getElseRegion();
if (elseRegion != null) {
@@ -1,5 +1,6 @@
package jadx.core.dex.visitors.regions;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.IContainer;
@@ -28,23 +29,33 @@ import org.slf4j.LoggerFactory;
* Extract blocks to separate try/catch region
*/
public class ProcessTryCatchRegions extends AbstractRegionVisitor {
private static final Logger LOG = LoggerFactory.getLogger(ProcessTryCatchRegions.class);
private static final boolean DEBUG = false;
static {
if (DEBUG) {
LOG.debug("Debug enabled for " + ProcessTryCatchRegions.class);
}
}
private final Map<BlockNode, TryCatchBlock> tryBlocksMap = new HashMap<BlockNode, TryCatchBlock>(2);
public ProcessTryCatchRegions(MethodNode mth) {
public static void process(MethodNode mth) {
if (mth.isNoCode() || mth.isNoExceptionHandlers()) {
return;
}
Set<TryCatchBlock> tryBlocks = new HashSet<TryCatchBlock>();
final Map<BlockNode, TryCatchBlock> tryBlocksMap = new HashMap<BlockNode, TryCatchBlock>(2);
searchTryCatchDominators(mth, tryBlocksMap);
int k = 0;
while (!tryBlocksMap.isEmpty()) {
DepthRegionTraversal.traverseAll(mth, new AbstractRegionVisitor() {
@Override
public void leaveRegion(MethodNode mth, IRegion region) {
checkAndWrap(mth, tryBlocksMap, region);
}
});
if (k++ > 100) {
throw new JadxRuntimeException("Try/catch wrap count limit reached in " + mth);
}
}
}
private static void searchTryCatchDominators(MethodNode mth, Map<BlockNode, TryCatchBlock> tryBlocksMap) {
final Set<TryCatchBlock> tryBlocks = new HashSet<TryCatchBlock>();
// collect all try/catch blocks
for (BlockNode block : mth.getBasicBlocks()) {
CatchAttr c = block.get(AType.CATCH_BLOCK);
@@ -67,7 +78,6 @@ public class ProcessTryCatchRegions extends AbstractRegionVisitor {
}
}
}
assert bs != null;
// intersect to get dominator of dominators
List<BlockNode> domBlocks = BlockUtils.bitSetToBlocks(mth, bs);
@@ -87,24 +97,19 @@ public class ProcessTryCatchRegions extends AbstractRegionVisitor {
LOG.info("!!! TODO: merge try blocks in " + mth);
}
}
if (DEBUG && !tryBlocksMap.isEmpty()) {
LOG.debug("ProcessTryCatchRegions: \n {} \n {}", mth, tryBlocksMap);
}
}
@Override
public void leaveRegion(MethodNode mth, IRegion region) {
if (tryBlocksMap.isEmpty() || !(region instanceof Region)) {
return;
}
private static void checkAndWrap(MethodNode mth, Map<BlockNode, TryCatchBlock> tryBlocksMap, IRegion region) {
// search dominator blocks in this region (don't need to go deeper)
for (BlockNode dominator : tryBlocksMap.keySet()) {
for (Map.Entry<BlockNode, TryCatchBlock> entry : tryBlocksMap.entrySet()) {
BlockNode dominator = entry.getKey();
if (region.getSubBlocks().contains(dominator)) {
wrapBlocks(region, dominator);
TryCatchBlock tb = tryBlocksMap.get(dominator);
if (!wrapBlocks(region, tb, dominator)) {
LOG.warn("Can't wrap try/catch for {}, method: {}", dominator, mth);
mth.add(AFlag.INCONSISTENT_CODE);
}
tryBlocksMap.remove(dominator);
// if region is modified rerun this method
leaveRegion(mth, region);
return;
}
}
@@ -113,12 +118,10 @@ public class ProcessTryCatchRegions extends AbstractRegionVisitor {
/**
* Extract all block dominated by 'dominator' to separate region and mark as try/catch block
*/
private void wrapBlocks(IRegion region, BlockNode dominator) {
private static boolean wrapBlocks(IRegion region, TryCatchBlock tb, BlockNode dominator) {
Region newRegion = new Region(region);
TryCatchBlock tb = tryBlocksMap.get(dominator);
assert tb != null;
for (IContainer cont : region.getSubBlocks()) {
List<IContainer> subBlocks = region.getSubBlocks();
for (IContainer cont : subBlocks) {
if (RegionUtils.isDominatedBy(dominator, cont)) {
if (isHandlerPath(tb, cont)) {
break;
@@ -127,16 +130,14 @@ public class ProcessTryCatchRegions extends AbstractRegionVisitor {
}
}
if (newRegion.getSubBlocks().isEmpty()) {
return;
}
if (DEBUG) {
LOG.debug("ProcessTryCatchRegions mark: {}", newRegion);
return false;
}
// replace first node by region
IContainer firstNode = newRegion.getSubBlocks().get(0);
int i = region.getSubBlocks().indexOf(firstNode);
region.getSubBlocks().set(i, newRegion);
region.getSubBlocks().removeAll(newRegion.getSubBlocks());
if (!region.replaceSubBlock(firstNode, newRegion)) {
return false;
}
subBlocks.removeAll(newRegion.getSubBlocks());
newRegion.addAttr(tb.getCatchAttr());
@@ -147,9 +148,10 @@ public class ProcessTryCatchRegions extends AbstractRegionVisitor {
aReg.setParent(newRegion);
}
}
return true;
}
private boolean isHandlerPath(TryCatchBlock tb, IContainer cont) {
private static boolean isHandlerPath(TryCatchBlock tb, IContainer cont) {
for (ExceptionHandler h : tb.getHandlers()) {
if (RegionUtils.hasPathThruBlock(h.getHandlerBlock(), cont)) {
return true;
@@ -157,5 +159,4 @@ public class ProcessTryCatchRegions extends AbstractRegionVisitor {
}
return false;
}
}
@@ -200,10 +200,15 @@ public class ProcessVariables extends AbstractVisitor {
it.remove();
}
}
if (set.isEmpty()) {
IRegion region = null;
if (!set.isEmpty()) {
region = set.iterator().next();
} else if (!u.getAssigns().isEmpty()) {
region = u.getAssigns().iterator().next();
}
if (region == null) {
continue;
}
IRegion region = set.iterator().next();
IRegion parent = region;
boolean declare = false;
while (parent != null) {
@@ -215,7 +220,6 @@ public class ProcessVariables extends AbstractVisitor {
region = parent;
parent = region.getParent();
}
if (!declare) {
declareVar(mth.getRegion(), u.getArg());
}
@@ -8,13 +8,11 @@ import jadx.core.dex.instructions.IfNode;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.SwitchNode;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.Edge;
import jadx.core.dex.nodes.IRegion;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.regions.IfCondition;
import jadx.core.dex.regions.IfInfo;
import jadx.core.dex.regions.IfRegion;
import jadx.core.dex.regions.LoopRegion;
@@ -42,10 +40,11 @@ import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static jadx.core.dex.regions.IfCondition.Mode;
import static jadx.core.dex.visitors.regions.IfMakerHelper.makeIfInfo;
import static jadx.core.dex.visitors.regions.IfMakerHelper.mergeNestedIfNodes;
import static jadx.core.utils.BlockUtils.getBlockByOffset;
import static jadx.core.utils.BlockUtils.getNextBlock;
import static jadx.core.utils.BlockUtils.isPathExists;
import static jadx.core.utils.BlockUtils.selectOther;
public class RegionMaker {
private static final Logger LOG = LoggerFactory.getLogger(RegionMaker.class);
@@ -110,34 +109,31 @@ public class RegionMaker {
}
}
if (!processed) {
int size = block.getInstructions().size();
if (size == 1) {
InsnNode insn = block.getInstructions().get(0);
switch (insn.getType()) {
case IF:
next = processIf(r, block, (IfNode) insn, stack);
processed = true;
break;
if (!processed && block.getInstructions().size() == 1) {
InsnNode insn = block.getInstructions().get(0);
switch (insn.getType()) {
case IF:
next = processIf(r, block, (IfNode) insn, stack);
processed = true;
break;
case SWITCH:
next = processSwitch(r, block, (SwitchNode) insn, stack);
processed = true;
break;
case SWITCH:
next = processSwitch(r, block, (SwitchNode) insn, stack);
processed = true;
break;
case MONITOR_ENTER:
next = processMonitorEnter(r, block, insn, stack);
processed = true;
break;
case MONITOR_ENTER:
next = processMonitorEnter(r, block, insn, stack);
processed = true;
break;
default:
break;
}
default:
break;
}
}
if (!processed) {
r.getSubBlocks().add(block);
next = BlockUtils.getNextBlock(block);
next = getNextBlock(block);
}
if (next != null && !stack.containsExit(block) && !stack.containsExit(next)) {
return next;
@@ -150,10 +146,10 @@ public class RegionMaker {
BlockNode loopStart = loop.getStart();
Set<BlockNode> exitBlocksSet = loop.getExitNodes();
// set exit blocks scan order by priority
// set exit blocks scan order priority
// this can help if loop have several exits (after using 'break' or 'return' in loop)
List<BlockNode> exitBlocks = new ArrayList<BlockNode>(exitBlocksSet.size());
BlockNode nextStart = BlockUtils.getNextBlock(loopStart);
BlockNode nextStart = getNextBlock(loopStart);
if (nextStart != null && exitBlocksSet.remove(nextStart)) {
exitBlocks.add(nextStart);
}
@@ -164,163 +160,185 @@ public class RegionMaker {
exitBlocks.add(loop.getEnd());
}
exitBlocks.addAll(exitBlocksSet);
exitBlocksSet = null;
IfNode ifnode = null;
LoopRegion loopRegion = null;
// exit block with loop condition
BlockNode condBlock = null;
// first block in loop
BlockNode bThen = null;
for (BlockNode exit : exitBlocks) {
if (exit.contains(AType.EXC_HANDLER)
|| exit.getInstructions().size() != 1) {
continue;
}
InsnNode insn = exit.getInstructions().get(0);
if (insn.getType() != InsnType.IF) {
continue;
}
ifnode = (IfNode) insn;
condBlock = exit;
loopRegion = new LoopRegion(curRegion, condBlock, condBlock == loop.getEnd());
boolean found = true;
if (condBlock != loopStart && condBlock != loop.getEnd()) {
if (condBlock.getPredecessors().contains(loopStart)) {
loopRegion.setPreCondition(loopStart);
// if we can't merge pre-condition this is not correct header
found = loopRegion.checkPreCondition();
} else {
found = false;
}
}
if (!found) {
ifnode = null;
loopRegion = null;
condBlock = null;
// try another exit
continue;
}
List<BlockNode> merged = new ArrayList<BlockNode>(2);
IfInfo mergedIf = mergeNestedIfNodes(condBlock, ifnode, merged);
if (mergedIf != null) {
condBlock = mergedIf.getIfnode();
if (!loop.getLoopBlocks().contains(mergedIf.getThenBlock())) {
// invert loop condition if it points to exit
loopRegion.setCondition(IfCondition.invert(mergedIf.getCondition()));
bThen = mergedIf.getElseBlock();
} else {
loopRegion.setCondition(mergedIf.getCondition());
bThen = mergedIf.getThenBlock();
}
exitBlocks.removeAll(merged);
}
break;
}
// endless loop
LoopRegion loopRegion = makeLoopRegion(curRegion, loop, exitBlocks);
if (loopRegion == null) {
return makeEndlessLoop(curRegion, stack, loop, loopStart);
}
if (bThen == null) {
bThen = ifnode.getThenBlock();
}
BlockNode loopBody = null;
for (BlockNode s : condBlock.getSuccessors()) {
if (loop.getLoopBlocks().contains(s)) {
loopBody = s;
break;
}
}
curRegion.getSubBlocks().add(loopRegion);
IRegion outerRegion = stack.peekRegion();
stack.push(loopRegion);
exitBlocks.remove(condBlock);
IfInfo info = makeIfInfo(loopRegion.getHeader());
IfInfo condInfo = mergeNestedIfNodes(info);
if (condInfo == null) {
condInfo = info;
}
if (!loop.getLoopBlocks().contains(condInfo.getThenBlock())) {
// invert loop condition if 'then' points to exit
condInfo = IfInfo.invert(condInfo);
}
loopRegion.setCondition(condInfo.getCondition());
exitBlocks.removeAll(condInfo.getMergedBlocks());
if (exitBlocks.size() > 0) {
BlockNode loopExit = BlockUtils.selectOtherSafe(loopBody, condBlock.getCleanSuccessors());
BlockNode loopExit = condInfo.getElseBlock();
if (loopExit != null) {
// add 'break' instruction before path cross between main loop exit and subexit
for (Edge exitEdge : loop.getExitEdges()) {
if (!exitBlocks.contains(exitEdge.getSource())) {
continue;
}
insertBreak(stack, loopExit, exitEdge);
tryInsertBreak(stack, loopExit, exitEdge);
}
}
}
BlockNode out;
if (loopRegion.isConditionAtEnd()) {
BlockNode bElse = ifnode.getElseBlock();
out = (bThen == loopStart ? bElse : bThen);
BlockNode thenBlock = condInfo.getThenBlock();
out = (thenBlock == loopStart ? condInfo.getElseBlock() : thenBlock);
loopStart.remove(AType.LOOP);
loop.getEnd().add(AFlag.SKIP);
stack.addExit(loop.getEnd());
loopRegion.setBody(makeRegion(loopStart, stack));
loopStart.addAttr(AType.LOOP, loop);
loop.getEnd().remove(AFlag.SKIP);
} else {
if (bThen != loopBody) {
loopRegion.setCondition(IfCondition.invert(loopRegion.getCondition()));
}
out = selectOther(loopBody, condBlock.getSuccessors());
if (out.contains(AFlag.LOOP_START)
out = condInfo.getElseBlock();
if (outerRegion != null
&& out.contains(AFlag.LOOP_START)
&& !out.getAll(AType.LOOP).contains(loop)
&& stack.peekRegion() instanceof LoopRegion) {
LoopRegion outerLoop = (LoopRegion) stack.peekRegion();
boolean notYetProcessed = outerLoop.getBody() == null;
if (notYetProcessed || RegionUtils.isRegionContainsBlock(outerLoop, out)) {
// exit to outer loop which already processed
out = null;
}
&& RegionUtils.isRegionContainsBlock(outerRegion, out)) {
// exit to already processed outer loop
out = null;
}
stack.addExit(out);
loopRegion.setBody(makeRegion(loopBody, stack));
BlockNode loopBody = condInfo.getThenBlock();
Region body = makeRegion(loopBody, stack);
// add blocks from loop start to first condition block
BlockNode conditionBlock = condInfo.getIfBlock();
if (loopStart != conditionBlock) {
Set<BlockNode> blocks = BlockUtils.getAllPathsBlocks(loopStart, conditionBlock);
blocks.remove(conditionBlock);
for (BlockNode block : blocks) {
if (block.getInstructions().isEmpty()
&& !block.contains(AFlag.SKIP)
&& !RegionUtils.isRegionContainsBlock(body, block)) {
body.add(block);
}
}
}
loopRegion.setBody(body);
}
stack.pop();
return out;
}
/**
* Select loop exit and construct LoopRegion
*/
private LoopRegion makeLoopRegion(IRegion curRegion, LoopInfo loop, List<BlockNode> exitBlocks) {
for (BlockNode block : exitBlocks) {
if (block.contains(AType.EXC_HANDLER)
|| block.getInstructions().size() != 1
|| block.getInstructions().get(0).getType() != InsnType.IF) {
continue;
}
LoopRegion loopRegion = new LoopRegion(curRegion, block, block == loop.getEnd());
boolean found;
if (block == loop.getStart() || block == loop.getEnd()
|| BlockUtils.isEmptySimplePath(loop.getStart(), block)) {
found = true;
} else if (block.getPredecessors().contains(loop.getStart())) {
loopRegion.setPreCondition(loop.getStart());
// if we can't merge pre-condition this is not correct header
found = loopRegion.checkPreCondition();
} else {
found = false;
}
if (found) {
return loopRegion;
}
}
// no exit found => endless loop
return null;
}
private BlockNode makeEndlessLoop(IRegion curRegion, RegionStack stack, LoopInfo loop, BlockNode loopStart) {
LoopRegion loopRegion;
loopRegion = new LoopRegion(curRegion, null, false);
LoopRegion loopRegion = new LoopRegion(curRegion, null, false);
curRegion.getSubBlocks().add(loopRegion);
loopStart.remove(AType.LOOP);
stack.push(loopRegion);
BlockNode loopExit = null;
// insert 'break' for exits
List<Edge> exitEdges = loop.getExitEdges();
if (exitEdges.size() == 1) {
for (Edge exitEdge : exitEdges) {
BlockNode exit = exitEdge.getTarget();
if (canInsertBreak(exit)) {
exit.getInstructions().add(new InsnNode(InsnType.BREAK, 0));
BlockNode nextBlock = getNextBlock(exit);
if (nextBlock != null) {
stack.addExit(nextBlock);
loopExit = nextBlock;
}
}
}
}
Region body = makeRegion(loopStart, stack);
if (!RegionUtils.isRegionContainsBlock(body, loop.getEnd())) {
body.getSubBlocks().add(loop.getEnd());
BlockNode loopEnd = loop.getEnd();
if (!RegionUtils.isRegionContainsBlock(body, loopEnd)
&& !loopEnd.contains(AType.EXC_HANDLER)) {
body.getSubBlocks().add(loopEnd);
}
loopRegion.setBody(body);
if (loopExit == null) {
BlockNode next = getNextBlock(loopEnd);
loopExit = RegionUtils.isRegionContainsBlock(body, next) ? null : next;
}
stack.pop();
loopStart.addAttr(AType.LOOP, loop);
BlockNode next = BlockUtils.getNextBlock(loop.getEnd());
return RegionUtils.isRegionContainsBlock(body, next) ? null : next;
return loopExit;
}
private void insertBreak(RegionStack stack, BlockNode loopExit, Edge exitEdge) {
private boolean canInsertBreak(BlockNode exit) {
if (exit.contains(AFlag.RETURN)) {
return false;
}
List<BlockNode> simplePath = BlockUtils.buildSimplePath(exit);
if (!simplePath.isEmpty()
&& simplePath.get(simplePath.size() - 1).contains(AFlag.RETURN)) {
return false;
}
// check if there no outer switch (TODO: very expensive check)
Set<BlockNode> paths = BlockUtils.getAllPathsBlocks(mth.getEnterBlock(), exit);
for (BlockNode block : paths) {
if (BlockUtils.checkLastInsnType(block, InsnType.SWITCH)) {
return false;
}
}
return true;
}
private void tryInsertBreak(RegionStack stack, BlockNode loopExit, Edge exitEdge) {
BlockNode prev = null;
BlockNode exit = exitEdge.getTarget();
while (exit != null) {
if (prev != null && isPathExists(loopExit, exit)) {
// found cross
if (!exit.contains(AFlag.RETURN)) {
if (canInsertBreak(exit)) {
prev.getInstructions().add(new InsnNode(InsnType.BREAK, 0));
stack.addExit(exit);
}
return;
}
prev = exit;
exit = BlockUtils.getNextBlock(exit);
exit = getNextBlock(exit);
}
}
@@ -339,18 +357,23 @@ public class RegionMaker {
InstructionRemover.unbindInsn(mth, exitInsn);
}
block = BlockUtils.getNextBlock(block);
BlockNode body = getNextBlock(block);
if (body == null) {
mth.add(AFlag.INCONSISTENT_CODE);
LOG.warn("Unexpected end of synchronized block");
return null;
}
BlockNode exit;
if (exits.size() == 1) {
exit = BlockUtils.getNextBlock(exits.iterator().next());
exit = getNextBlock(exits.iterator().next());
} else {
cacheSet.clear();
exit = traverseMonitorExitsCross(block, exits, cacheSet);
exit = traverseMonitorExitsCross(body, exits, cacheSet);
}
stack.push(synchRegion);
stack.addExit(exit);
synchRegion.getSubBlocks().add(makeRegion(block, stack));
synchRegion.getSubBlocks().add(makeRegion(body, stack));
stack.pop();
return exit;
}
@@ -408,64 +431,35 @@ public class RegionMaker {
// block already included in other 'if' region
return ifnode.getThenBlock();
}
final BlockNode thenBlock;
final BlockNode elseBlock;
BlockNode out = null;
IfRegion ifRegion = new IfRegion(currentRegion, block);
currentRegion.getSubBlocks().add(ifRegion);
IfInfo mergedIf = mergeNestedIfNodes(block, ifnode, null);
IfInfo currentIf = makeIfInfo(block);
IfInfo mergedIf = mergeNestedIfNodes(currentIf);
if (mergedIf != null) {
ifRegion.setCondition(mergedIf.getCondition());
thenBlock = mergedIf.getThenBlock();
elseBlock = mergedIf.getElseBlock();
out = BlockUtils.getPathCross(mth, thenBlock, elseBlock);
currentIf = mergedIf;
} else {
// invert condition (compiler often do it)
ifnode.invertCondition();
final BlockNode bThen = ifnode.getThenBlock();
final BlockNode bElse = ifnode.getElseBlock();
// select 'then', 'else' and 'exit' blocks
if (bElse.getPredecessors().size() != 1
&& BlockUtils.isPathExists(bThen, bElse)) {
thenBlock = bThen;
elseBlock = null;
out = bElse;
} else if (bThen.getPredecessors().size() != 1
&& BlockUtils.isPathExists(bElse, bThen)) {
ifnode.invertCondition();
thenBlock = ifnode.getThenBlock();
elseBlock = null;
out = ifnode.getElseBlock();
} else if (block.getDominatesOn().size() == 2) {
thenBlock = bThen;
elseBlock = bElse;
out = BlockUtils.getPathCross(mth, bThen, bElse);
} else if (bElse.getPredecessors().size() != 1) {
thenBlock = bThen;
elseBlock = null;
out = bElse;
} else {
thenBlock = bThen;
elseBlock = bElse;
for (BlockNode d : block.getDominatesOn()) {
if (d != bThen && d != bElse) {
out = d;
break;
}
}
}
if (BlockUtils.isBackEdge(block, out)) {
out = null;
// invert simple condition (compiler often do it)
currentIf = IfInfo.invert(currentIf);
}
currentIf = IfMakerHelper.restructureIf(mth, block, currentIf);
if (currentIf == null) {
// invalid merged if, check simple one again
currentIf = makeIfInfo(block);
currentIf = IfMakerHelper.restructureIf(mth, block, currentIf);
if (currentIf == null) {
// all attempts failed
return null;
}
}
stack.push(ifRegion);
stack.addExit(out);
IfRegion ifRegion = new IfRegion(currentRegion, block);
ifRegion.setCondition(currentIf.getCondition());
currentRegion.getSubBlocks().add(ifRegion);
ifRegion.setThenRegion(makeRegion(thenBlock, stack));
stack.push(ifRegion);
stack.addExit(currentIf.getOutBlock());
ifRegion.setThenRegion(makeRegion(currentIf.getThenBlock(), stack));
BlockNode elseBlock = currentIf.getElseBlock();
if (elseBlock == null || stack.containsExit(elseBlock)) {
ifRegion.setElseRegion(null);
} else {
@@ -473,131 +467,7 @@ public class RegionMaker {
}
stack.pop();
return out;
}
private IfInfo mergeNestedIfNodes(BlockNode block, IfNode ifnode, List<BlockNode> merged) {
IfInfo info = new IfInfo();
info.setIfnode(block);
info.setCondition(IfCondition.fromIfBlock(block));
info.setThenBlock(ifnode.getThenBlock());
info.setElseBlock(ifnode.getElseBlock());
return mergeNestedIfNodes(info, merged);
}
private IfInfo mergeNestedIfNodes(IfInfo info, List<BlockNode> merged) {
BlockNode bThen = info.getThenBlock();
BlockNode bElse = info.getElseBlock();
if (bThen == bElse) {
return null;
}
BlockNode ifBlock = info.getIfnode();
BlockNode nestedIfBlock = getNextIfBlock(ifBlock);
if (nestedIfBlock == null) {
return null;
}
IfNode nestedIfInsn = (IfNode) nestedIfBlock.getInstructions().get(0);
IfCondition nestedCondition = IfCondition.fromIfNode(nestedIfInsn);
BlockNode nbThen = nestedIfInsn.getThenBlock();
BlockNode nbElse = nestedIfInsn.getElseBlock();
IfCondition condition = info.getCondition();
boolean inverted = false;
if (isPathExists(bElse, nestedIfBlock)) {
// else branch
if (!isEqualPaths(bThen, nbThen)) {
if (!isEqualPaths(bThen, nbElse)) {
// not connected conditions
return null;
}
nestedIfInsn.invertCondition();
inverted = true;
}
condition = IfCondition.merge(Mode.OR, condition, nestedCondition);
} else {
// then branch
if (!isEqualPaths(bElse, nbElse)) {
if (!isEqualPaths(bElse, nbThen)) {
// not connected conditions
return null;
}
nestedIfInsn.invertCondition();
inverted = true;
}
condition = IfCondition.merge(Mode.AND, condition, nestedCondition);
}
if (merged != null) {
merged.add(nestedIfBlock);
}
nestedIfBlock.add(AFlag.SKIP);
BlockNode blockToNestedIfBlock = BlockUtils.getNextBlockToPath(ifBlock, nestedIfBlock);
skipSimplePath(BlockUtils.selectOther(blockToNestedIfBlock, ifBlock.getCleanSuccessors()));
IfInfo result = new IfInfo();
result.setIfnode(nestedIfBlock);
result.setCondition(condition);
result.setThenBlock(inverted ? nbElse : nbThen);
result.setElseBlock(inverted ? nbThen : nbElse);
// search next nested if block
IfInfo next = mergeNestedIfNodes(result, merged);
if (next != null) {
return next;
}
return result;
}
private BlockNode getNextIfBlock(BlockNode block) {
for (BlockNode succ : block.getSuccessors()) {
BlockNode nestedIfBlock = getIfNode(succ);
if (nestedIfBlock != null && nestedIfBlock != block) {
return nestedIfBlock;
}
}
return null;
}
private static BlockNode getIfNode(BlockNode block) {
if (block != null && !block.contains(AType.LOOP)) {
List<InsnNode> insns = block.getInstructions();
if (insns.size() == 1 && insns.get(0).getType() == InsnType.IF) {
return block;
}
// skip block
List<BlockNode> successors = block.getSuccessors();
if (successors.size() == 1) {
BlockNode next = successors.get(0);
boolean pass = true;
if (block.getInstructions().size() != 0) {
for (InsnNode insn : block.getInstructions()) {
RegisterArg res = insn.getResult();
if (res == null) {
pass = false;
break;
}
List<RegisterArg> useList = res.getSVar().getUseList();
if (useList.size() != 1) {
pass = false;
break;
} else {
InsnArg arg = useList.get(0);
InsnNode usePlace = arg.getParentInsn();
if (!BlockUtils.blockContains(block, usePlace)
&& !BlockUtils.blockContains(next, usePlace)) {
pass = false;
break;
}
}
}
}
if (pass) {
return getIfNode(next);
}
}
}
return null;
return currentIf.getOutBlock();
}
private BlockNode processSwitch(IRegion currentRegion, BlockNode block, SwitchNode insn, RegionStack stack) {
@@ -627,7 +497,7 @@ public class RegionMaker {
BitSet succ = BlockUtils.blocksToBitSet(mth, block.getSuccessors());
BitSet domsOn = BlockUtils.blocksToBitSet(mth, block.getDominatesOn());
domsOn.and(succ); // filter 'out' block
domsOn.xor(succ); // filter 'out' block
BlockNode defCase = getBlockByOffset(insn.getDefaultCaseOffset(), block.getSuccessors());
if (defCase != null) {
@@ -642,13 +512,11 @@ public class RegionMaker {
}
if (outCount > 1) {
// filter successors of other blocks
List<BlockNode> blocks = mth.getBasicBlocks();
for (int i = domsOn.nextSetBit(0); i >= 0; i = domsOn.nextSetBit(i + 1)) {
BlockNode b = mth.getBasicBlocks().get(i);
BlockNode b = blocks.get(i);
for (BlockNode s : b.getCleanSuccessors()) {
int id = s.getId();
if (domsOn.get(id)) {
domsOn.clear(id);
}
domsOn.clear(s.getId());
}
}
outCount = domsOn.cardinality();
@@ -658,19 +526,27 @@ public class RegionMaker {
if (outCount == 1) {
out = mth.getBasicBlocks().get(domsOn.nextSetBit(0));
} else if (outCount == 0) {
// default and out blocks are same
out = defCase;
// one or several case blocks are empty,
// run expensive algorithm for find 'out' block
for (BlockNode maybeOut : block.getSuccessors()) {
boolean allReached = true;
for (BlockNode s : block.getSuccessors()) {
if (!isPathExists(s, maybeOut)) {
allReached = false;
break;
}
}
if (allReached) {
out = maybeOut;
break;
}
}
}
stack.push(sw);
if (out != null) {
stack.addExit(out);
}
// else {
// for (BlockNode e : BlockUtils.bitSetToBlocks(mth, domsOn)) {
// stack.addExit(e);
// }
// }
if (!stack.containsExit(defCase)) {
sw.setDefaultCase(makeRegion(defCase, stack));
@@ -709,13 +585,14 @@ public class RegionMaker {
Set<BlockNode> exits = new HashSet<BlockNode>();
for (BlockNode splitter : splitters) {
for (BlockNode handler : blocks) {
List<BlockNode> s = splitter.getCleanSuccessors();
List<BlockNode> s = splitter.getSuccessors();
if (s.isEmpty()) {
LOG.debug(ErrorsCounter.formatErrorMsg(mth, "No successors for splitter: " + splitter));
continue;
}
BlockNode cross = BlockUtils.getPathCross(mth, s.get(0), handler);
if (cross != null) {
BlockNode ss = s.get(0);
BlockNode cross = BlockUtils.getPathCross(mth, ss, handler);
if (cross != null && cross != ss && cross != handler) {
exits.add(cross);
}
}
@@ -737,22 +614,28 @@ public class RegionMaker {
RegionStack stack = new RegionStack(mth);
stack.addExits(exits);
BlockNode exit = BlockUtils.traverseWhileDominates(start, start);
if (exit != null && RegionUtils.isRegionContainsBlock(mth.getRegion(), exit)) {
stack.addExit(exit);
}
handler.setHandlerRegion(makeRegion(start, stack));
ExcHandlerAttr excHandlerAttr = start.get(AType.EXC_HANDLER);
handler.getHandlerRegion().addAttr(excHandlerAttr);
}
private void skipSimplePath(BlockNode block) {
static void skipSimplePath(BlockNode block) {
while (block != null
&& block.getCleanSuccessors().size() < 2
&& block.getPredecessors().size() == 1) {
block.add(AFlag.SKIP);
block = BlockUtils.getNextBlock(block);
block = getNextBlock(block);
}
}
private static boolean isEqualPaths(BlockNode b1, BlockNode b2) {
static boolean isEqualPaths(BlockNode b1, BlockNode b2) {
if (b1 == b2) {
return true;
}
@@ -772,8 +655,8 @@ public class RegionMaker {
if (!b1.isSynthetic() || !b2.isSynthetic()) {
return false;
}
BlockNode n1 = BlockUtils.getNextBlock(b1);
BlockNode n2 = BlockUtils.getNextBlock(b2);
BlockNode n1 = getNextBlock(b1);
BlockNode n2 = getNextBlock(b2);
return isEqualPaths(n1, n2);
}
@@ -42,9 +42,8 @@ public class RegionMakerVisitor extends AbstractVisitor {
private static void postProcessRegions(MethodNode mth) {
// make try-catch regions
if (!mth.isNoExceptionHandlers()) {
DepthRegionTraversal.traverse(mth, new ProcessTryCatchRegions(mth));
}
ProcessTryCatchRegions.process(mth);
// merge conditions in loops
if (mth.getLoopsCount() != 0) {
DepthRegionTraversal.traverseAll(mth, new AbstractRegionVisitor() {
@@ -12,13 +12,11 @@ import jadx.core.dex.regions.IfRegion;
import jadx.core.dex.regions.LoopRegion;
import jadx.core.dex.regions.SwitchRegion;
import jadx.core.dex.visitors.AbstractVisitor;
import jadx.core.utils.RegionUtils;
import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.exceptions.JadxRuntimeException;
import java.util.HashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Set;
/**
* Remove unnecessary return instructions for void methods
@@ -45,7 +43,7 @@ public class ReturnVisitor extends AbstractVisitor {
if (insns.size() == 1
&& blockNotInLoop(mth, block)
&& noTrailInstructions(block)) {
insns.remove(insns.size() - 1);
insns.remove(0);
block.remove(AFlag.RETURN);
}
}
@@ -85,7 +83,7 @@ public class ReturnVisitor extends AbstractVisitor {
IContainer subBlock = itSubBlock.previous();
if (subBlock == curContainer) {
break;
} else if (notEmpty(subBlock)) {
} else if (!isEmpty(subBlock)) {
return false;
}
}
@@ -95,25 +93,25 @@ public class ReturnVisitor extends AbstractVisitor {
return true;
}
private static boolean notEmpty(IContainer subBlock) {
if (subBlock.contains(AFlag.RETURN)) {
return false;
}
int insnCount = RegionUtils.insnsCount(subBlock);
if (insnCount > 1) {
return true;
}
if (insnCount == 1) {
// don't count one 'return' instruction (it will be removed later)
Set<BlockNode> blocks = new HashSet<BlockNode>();
RegionUtils.getAllRegionBlocks(subBlock, blocks);
for (BlockNode node : blocks) {
if (!node.contains(AFlag.RETURN) && !node.getInstructions().isEmpty()) {
return true;
/**
* Check if container not contains instructions,
* don't count one 'return' instruction (it will be removed later).
*/
private static boolean isEmpty(IContainer container) {
if (container instanceof BlockNode) {
BlockNode block = (BlockNode) container;
return block.getInstructions().isEmpty() || block.contains(AFlag.RETURN);
} else if (container instanceof IRegion) {
IRegion region = (IRegion) container;
for (IContainer block : region.getSubBlocks()) {
if (!isEmpty(block)) {
return false;
}
}
return true;
} else {
throw new JadxRuntimeException("Unknown container type: " + container.getClass());
}
return false;
}
}
}
@@ -12,12 +12,12 @@ public abstract class TracedRegionVisitor implements IRegionVisitor {
protected final Deque<IRegion> regionStack = new ArrayDeque<IRegion>();
@Override
public void enterRegion(MethodNode mth, IRegion region) {
public final void enterRegion(MethodNode mth, IRegion region) {
regionStack.push(region);
}
@Override
public void processBlock(MethodNode mth, IBlock container) {
public final void processBlock(MethodNode mth, IBlock container) {
IRegion curRegion = regionStack.peek();
processBlockTraced(mth, container, curRegion);
}
@@ -25,7 +25,7 @@ public abstract class TracedRegionVisitor implements IRegionVisitor {
public abstract void processBlockTraced(MethodNode mth, IBlock container, IRegion currentRegion);
@Override
public void leaveRegion(MethodNode mth, IRegion region) {
public final void leaveRegion(MethodNode mth, IRegion region) {
regionStack.pop();
}
}
@@ -20,7 +20,6 @@ public class LiveVarAnalysis {
public LiveVarAnalysis(MethodNode mth) {
this.mth = mth;
runAnalysis();
}
public void runAnalysis() {
@@ -32,17 +32,18 @@ public class SSATransform extends AbstractVisitor {
process(mth);
}
private void process(MethodNode mth) {
private static void process(MethodNode mth) {
LiveVarAnalysis la = new LiveVarAnalysis(mth);
la.runAnalysis();
for (int i = 0; i < mth.getRegsCount(); i++) {
int regsCount = mth.getRegsCount();
for (int i = 0; i < regsCount; i++) {
placePhi(mth, i, la);
}
renameVariables(mth);
removeUselessPhi(mth);
}
private void placePhi(MethodNode mth, int regNum, LiveVarAnalysis la) {
private static void placePhi(MethodNode mth, int regNum, LiveVarAnalysis la) {
List<BlockNode> blocks = mth.getBasicBlocks();
int blocksCount = blocks.size();
BitSet hasPhi = new BitSet(blocksCount);
@@ -71,7 +72,7 @@ public class SSATransform extends AbstractVisitor {
}
}
private void addPhi(BlockNode block, int regNum) {
private static void addPhi(BlockNode block, int regNum) {
PhiListAttr phiList = block.get(AType.PHI_LIST);
if (phiList == null) {
phiList = new PhiListAttr();
@@ -82,7 +83,7 @@ public class SSATransform extends AbstractVisitor {
block.getInstructions().add(0, phiInsn);
}
private void renameVariables(MethodNode mth) {
private static void renameVariables(MethodNode mth) {
int regsCount = mth.getRegsCount();
SSAVar[] vars = new SSAVar[regsCount];
int[] versions = new int[regsCount];
@@ -94,7 +95,7 @@ public class SSATransform extends AbstractVisitor {
renameVar(mth, vars, versions, mth.getEnterBlock());
}
private void renameVar(MethodNode mth, SSAVar[] vars, int[] vers, BlockNode block) {
private static void renameVar(MethodNode mth, SSAVar[] vars, int[] vers, BlockNode block) {
SSAVar[] inputVars = Arrays.copyOf(vars, vars.length);
for (InsnNode insn : block.getInstructions()) {
if (insn.getType() != InsnType.PHI) {
@@ -142,7 +143,7 @@ public class SSATransform extends AbstractVisitor {
System.arraycopy(inputVars, 0, vars, 0, vars.length);
}
private void removeUselessPhi(MethodNode mth) {
private static void removeUselessPhi(MethodNode mth) {
List<PhiInsn> insnToRemove = new ArrayList<PhiInsn>();
for (SSAVar var : mth.getSVars()) {
// phi result not used
@@ -165,7 +166,7 @@ public class SSATransform extends AbstractVisitor {
removePhiList(mth, insnToRemove);
}
private void removePhiWithSameArgs(PhiInsn phi, List<PhiInsn> insnToRemove) {
private static void removePhiWithSameArgs(PhiInsn phi, List<PhiInsn> insnToRemove) {
if (phi.getArgsCount() <= 1) {
insnToRemove.add(phi);
return;
@@ -189,7 +190,7 @@ public class SSATransform extends AbstractVisitor {
}
}
private void removePhiList(MethodNode mth, List<PhiInsn> insnToRemove) {
private static void removePhiList(MethodNode mth, List<PhiInsn> insnToRemove) {
if (insnToRemove.isEmpty()) {
return;
}
@@ -19,7 +19,7 @@ public class FinishTypeInference extends AbstractVisitor {
change = false;
for (BlockNode block : mth.getBasicBlocks()) {
for (InsnNode insn : block.getInstructions()) {
if (PostTypeInference.visit(mth, insn)) {
if (PostTypeInference.process(mth, insn)) {
change = true;
}
}
@@ -15,7 +15,7 @@ import java.util.List;
public class PostTypeInference {
public static boolean visit(MethodNode mth, InsnNode insn) {
public static boolean process(MethodNode mth, InsnNode insn) {
switch (insn.getType()) {
case CONST:
RegisterArg res = insn.getResult();
@@ -0,0 +1,29 @@
package jadx.core.utils;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import org.objectweb.asm.ClassReader;
public class AsmUtils {
private AsmUtils() {
}
public static String getNameFromClassFile(File file) throws IOException {
String className = null;
FileInputStream in = null;
try {
in = new FileInputStream(file);
ClassReader classReader = new ClassReader(in);
className = classReader.getClassName();
} finally {
if (in != null) {
in.close();
}
}
return className;
}
}
@@ -9,7 +9,9 @@ import jadx.core.utils.exceptions.JadxRuntimeException;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
@@ -101,13 +103,13 @@ public class BlockUtils {
return false;
}
public static boolean lastInsnType(BlockNode block, InsnType type) {
public static boolean checkLastInsnType(BlockNode block, InsnType expectedType) {
List<InsnNode> insns = block.getInstructions();
if (insns.isEmpty()) {
return false;
}
InsnNode insn = insns.get(insns.size() - 1);
return insn.getType() == type;
return insn.getType() == expectedType;
}
public static BlockNode getBlockByInsn(MethodNode mth, InsnNode insn) {
@@ -203,15 +205,40 @@ public class BlockUtils {
}
public static boolean isPathExists(BlockNode start, BlockNode end) {
if (start == end) {
if (start == end
|| end.isDominator(start)
|| start.getCleanSuccessors().contains(end)) {
return true;
}
if (end.isDominator(start)) {
return true;
if (start.getPredecessors().contains(end)) {
return false;
}
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) {
if (start == end) {
return true;
@@ -251,7 +278,7 @@ public class BlockUtils {
}
BitSet b = new BitSet();
b.or(b1.getDomFrontier());
b.or(b2.getDomFrontier());
b.and(b2.getDomFrontier());
b.clear(b1.getId());
b.clear(b2.getId());
if (b.cardinality() == 1) {
@@ -260,6 +287,12 @@ public class BlockUtils {
return end;
}
}
if (isPathExists(b1, b2)) {
return b2;
}
if (isPathExists(b2, b1)) {
return b1;
}
return null;
}
@@ -280,4 +313,36 @@ public class BlockUtils {
}
}
}
public static List<BlockNode> buildSimplePath(BlockNode block) {
List<BlockNode> list = new LinkedList<BlockNode>();
while (block != null
&& block.getCleanSuccessors().size() < 2
&& block.getPredecessors().size() == 1) {
list.add(block);
block = getNextBlock(block);
}
return list.isEmpty() ? Collections.<BlockNode>emptyList() : list;
}
/**
* Return true if on path from start to end no instructions and no branches.
*/
public static boolean isEmptySimplePath(BlockNode start, BlockNode end) {
if (start == end && start.getInstructions().isEmpty()) {
return true;
}
if (!start.getInstructions().isEmpty() || start.getCleanSuccessors().size() != 1) {
return false;
}
BlockNode block = getNextBlock(start);
while (block != null
&& block != end
&& block.getCleanSuccessors().size() < 2
&& block.getPredecessors().size() == 1
&& block.getInstructions().isEmpty()) {
block = getNextBlock(block);
}
return block == end;
}
}
@@ -7,7 +7,11 @@ import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.exceptions.JadxOverflowException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.slf4j.Logger;
@@ -16,20 +20,20 @@ import org.slf4j.LoggerFactory;
public class ErrorsCounter {
private static final Logger LOG = LoggerFactory.getLogger(ErrorsCounter.class);
private static final Set<Object> ERROR_NODES = new HashSet<Object>();
private static int errorsCount;
private final Set<Object> errorNodes = new HashSet<Object>();
private int errorsCount;
public static int getErrorCount() {
public int getErrorCount() {
return errorsCount;
}
public static void reset() {
ERROR_NODES.clear();
public void reset() {
errorNodes.clear();
errorsCount = 0;
}
private static void addError(IAttributeNode node, String msg, Throwable e) {
ERROR_NODES.add(node);
private void addError(IAttributeNode node, String msg, Throwable e) {
errorNodes.add(node);
errorsCount++;
if (e != null) {
@@ -49,13 +53,13 @@ public class ErrorsCounter {
public static String classError(ClassNode cls, String errorMsg, Throwable e) {
String msg = formatErrorMsg(cls, errorMsg);
addError(cls, msg, e);
cls.dex().root().getErrorsCounter().addError(cls, msg, e);
return msg;
}
public static String methodError(MethodNode mth, String errorMsg, Throwable e) {
String msg = formatErrorMsg(mth, errorMsg);
addError(mth, msg, e);
mth.dex().root().getErrorsCounter().addError(mth, msg, e);
return msg;
}
@@ -63,10 +67,17 @@ public class ErrorsCounter {
return methodError(mth, errorMsg, null);
}
public static void printReport() {
public void printReport() {
if (getErrorCount() > 0) {
LOG.error(getErrorCount() + " errors occured in following nodes:");
for (Object node : ERROR_NODES) {
List<Object> nodes = new ArrayList<Object>(errorNodes);
Collections.sort(nodes, new Comparator<Object>() {
@Override
public int compare(Object o1, Object o2) {
return String.valueOf(o1).compareTo(String.valueOf(o2));
}
});
for (Object node : nodes) {
String nodeName = node.getClass().getSimpleName().replace("Node", "");
LOG.error(" " + nodeName + ": " + node);
}
@@ -81,7 +92,7 @@ public class ErrorsCounter {
return msg + " in method: " + mth;
}
private static String formatException(Throwable e) {
private String formatException(Throwable e) {
if (e == null || e.getMessage() == null) {
return "";
} else {
@@ -89,11 +100,11 @@ public class ErrorsCounter {
}
}
public static String formatErrorMsg(ClassNode cls, String msg, Throwable e) {
public String formatErrorMsg(ClassNode cls, String msg, Throwable e) {
return formatErrorMsg(cls, msg) + formatException(e);
}
public static String formatErrorMsg(MethodNode mth, String msg, Throwable e) {
public String formatErrorMsg(MethodNode mth, String msg, Throwable e) {
return formatErrorMsg(mth, msg) + formatException(e);
}
}
@@ -26,17 +26,23 @@ public class InstructionRemover {
private static final Logger LOG = LoggerFactory.getLogger(InstructionRemover.class);
private final MethodNode mth;
private final List<InsnNode> insns;
private final List<InsnNode> toRemove;
private List<InsnNode> instrList;
public InstructionRemover(MethodNode mth, BlockNode block) {
this(mth, block.getInstructions());
public InstructionRemover(MethodNode mth) {
this(mth, null);
}
public InstructionRemover(MethodNode mth, List<InsnNode> instructions) {
public InstructionRemover(MethodNode mth, BlockNode block) {
this.mth = mth;
this.insns = instructions;
this.toRemove = new ArrayList<InsnNode>();
if (block != null) {
this.instrList = block.getInstructions();
}
}
public void setBlock(BlockNode block) {
this.instrList = block.getInstructions();
}
public void add(InsnNode insn) {
@@ -47,7 +53,7 @@ public class InstructionRemover {
if (toRemove.isEmpty()) {
return;
}
removeAll(insns, toRemove);
removeAll(mth, instrList, toRemove);
toRemove.clear();
}
@@ -84,9 +90,9 @@ public class InstructionRemover {
}
}
// Don't use 'insns.removeAll(toRemove)' because it will remove instructions by content
// Don't use 'instrList.removeAll(toRemove)' because it will remove instructions by content
// and here can be several instructions with same content
private void removeAll(List<InsnNode> insns, List<InsnNode> toRemove) {
private static void removeAll(MethodNode mth, List<InsnNode> insns, List<InsnNode> toRemove) {
for (InsnNode rem : toRemove) {
unbindInsn(mth, rem);
int insnsCount = insns.size();
@@ -119,6 +125,10 @@ public class InstructionRemover {
}
}
public static void removeAll(MethodNode mth, BlockNode block, List<InsnNode> insns) {
removeAll(mth, block.getInstructions(), insns);
}
public static void remove(MethodNode mth, BlockNode block, int index) {
List<InsnNode> instructions = block.getInstructions();
unbindInsn(mth, instructions.get(index));
@@ -2,6 +2,7 @@ package jadx.core.utils;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.IContainer;
import jadx.core.dex.nodes.IRegion;
@@ -39,7 +40,7 @@ public class RegionUtils {
*/
public static boolean hasExitBlock(IContainer container) {
if (container instanceof BlockNode) {
return ((BlockNode) container).getSuccessors().size() == 0;
return ((BlockNode) container).getSuccessors().isEmpty();
} else if (container instanceof IRegion) {
List<IContainer> blocks = ((IRegion) container).getSubBlocks();
return !blocks.isEmpty()
@@ -49,6 +50,18 @@ public class RegionUtils {
}
}
public static boolean hasBreakInsn(IContainer container) {
if (container instanceof BlockNode) {
return BlockUtils.checkLastInsnType((BlockNode) container, InsnType.BREAK);
} else if (container instanceof IRegion) {
List<IContainer> blocks = ((IRegion) container).getSubBlocks();
return !blocks.isEmpty()
&& hasBreakInsn(blocks.get(blocks.size() - 1));
} else {
throw new JadxRuntimeException("Unknown container type: " + container);
}
}
public static int insnsCount(IContainer container) {
if (container instanceof BlockNode) {
return ((BlockNode) container).getInstructions().size();
@@ -1,6 +1,9 @@
package jadx.core.utils.exceptions;
public class JadxOverflowException extends JadxRuntimeException {
private static final long serialVersionUID = 2568659798680154204L;
public JadxOverflowException(String message) {
super(message);
}
@@ -0,0 +1,38 @@
package jadx.core.utils.files;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;
public class FileUtils {
private FileUtils() {
}
public static void addFileToJar(JarOutputStream jar, File source, String entryName) throws IOException {
BufferedInputStream in = null;
try {
JarEntry entry = new JarEntry(entryName);
entry.setTime(source.lastModified());
jar.putNextEntry(entry);
in = new BufferedInputStream(new FileInputStream(source));
byte[] buffer = new byte[8192];
while (true) {
int count = in.read(buffer);
if (count == -1) {
break;
}
jar.write(buffer, 0, count);
}
jar.closeEntry();
} finally {
if (in != null) {
in.close();
}
}
}
}
@@ -1,13 +1,15 @@
package jadx.core.utils.files;
import jadx.core.utils.AsmUtils;
import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.exceptions.JadxRuntimeException;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.jar.JarOutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
@@ -35,37 +37,44 @@ public class InputFile {
if (fileName.endsWith(".dex")) {
return new Dex(file);
}
if (fileName.endsWith(".class")) {
return loadFromClassFile(file);
}
if (fileName.endsWith(".apk")) {
byte[] data = openDexFromZip(file);
if (data == null) {
throw new JadxRuntimeException("File 'classes.dex' not found in file: " + file);
Dex dex = loadFromZip(file);
if (dex == null) {
throw new IOException("File 'classes.dex' not found in file: " + file);
}
return new Dex(data);
return dex;
}
if (fileName.endsWith(".jar")) {
// check if jar contains 'classes.dex'
byte[] data = openDexFromZip(file);
if (data != null) {
return new Dex(data);
}
try {
LOG.info("converting to dex: {} ...", fileName);
JavaToDex j2d = new JavaToDex();
byte[] ba = j2d.convert(file.getAbsolutePath());
if (ba.length == 0) {
throw new JadxException(j2d.isError() ? j2d.getDxErrors() : "Empty dx output");
} else if (j2d.isError()) {
LOG.warn("dx message: " + j2d.getDxErrors());
}
return new Dex(ba);
} catch (Throwable e) {
throw new DecodeException("java class to dex conversion error:\n " + e.getMessage(), e);
Dex dex = loadFromZip(file);
if (dex != null) {
return dex;
}
return loadFromJar(file);
}
throw new DecodeException("Unsupported input file format: " + file);
}
private byte[] openDexFromZip(File file) throws IOException {
private static Dex loadFromJar(File jarFile) throws DecodeException {
try {
LOG.info("converting to dex: {} ...", jarFile.getName());
JavaToDex j2d = new JavaToDex();
byte[] ba = j2d.convert(jarFile.getAbsolutePath());
if (ba.length == 0) {
throw new JadxException(j2d.isError() ? j2d.getDxErrors() : "Empty dx output");
} else if (j2d.isError()) {
LOG.warn("dx message: " + j2d.getDxErrors());
}
return new Dex(ba);
} catch (Throwable e) {
throw new DecodeException("java class to dex conversion error:\n " + e.getMessage(), e);
}
}
private static Dex loadFromZip(File file) throws IOException {
ZipFile zf = new ZipFile(file);
ZipEntry dex = zf.getEntry("classes.dex");
if (dex == null) {
@@ -87,7 +96,31 @@ public class InputFile {
}
zf.close();
}
return bytesOut.toByteArray();
return new Dex(bytesOut.toByteArray());
}
private static Dex loadFromClassFile(File file) throws IOException, DecodeException {
File outFile = File.createTempFile("jadx-tmp-", System.nanoTime() + ".jar");
outFile.deleteOnExit();
FileOutputStream out = null;
JarOutputStream jo = null;
try {
out = new FileOutputStream(outFile);
jo = new JarOutputStream(out);
String clsName = AsmUtils.getNameFromClassFile(file);
if (clsName == null) {
throw new IOException("Can't read class name from file: " + file);
}
FileUtils.addFileToJar(jo, file, clsName + ".class");
} finally {
if (jo != null) {
jo.close();
}
if (out != null) {
out.close();
}
}
return loadFromJar(outFile);
}
public File getFile() {
@@ -1,9 +1,6 @@
package jadx.tests
import jadx.api.JadxDecompiler
import jadx.api.IJadxArgs
import jadx.core.dex.nodes.MethodNode
import jadx.core.utils.ErrorsCounter
import jadx.api.JadxDecompiler
import jadx.core.utils.exceptions.JadxException
import jadx.core.utils.exceptions.JadxRuntimeException
import spock.lang.Specification
@@ -67,20 +64,4 @@ class TestAPI extends Specification {
expect:
new JadxDecompiler().getErrorsCount() == 0
}
def "get errors count after one more init"() {
setup:
new JadxDecompiler()
def mth = Mock(MethodNode)
when:
ErrorsCounter.methodError(mth, "")
def d = new JadxDecompiler()
then:
d.getErrorsCount() == 0
}
def "decompiler toString()"() {
expect:
new JadxDecompiler().toString() == "jadx decompiler"
}
}
@@ -7,17 +7,15 @@ import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.visitors.DepthTraversal;
import jadx.core.dex.visitors.IDexTreeVisitor;
import jadx.core.utils.files.FileUtils;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;
import static org.hamcrest.CoreMatchers.containsString;
@@ -31,6 +29,7 @@ import static org.junit.Assert.fail;
public abstract class InternalJadxTest extends TestUtils {
protected boolean outputCFG = false;
protected boolean isFallback = false;
protected boolean deleteTmpJar = true;
protected String outDir = "test-out-tmp";
@@ -53,6 +52,11 @@ public abstract class InternalJadxTest extends TestUtils {
}
// don't unload class
checkCode(cls);
return cls;
}
private static void checkCode(ClassNode cls) {
assertTrue("Inconsistent cls: " + cls,
!cls.contains(AFlag.INCONSISTENT_CODE) && !cls.contains(AType.JADX_ERROR));
for (MethodNode mthNode : cls.getMethods()) {
@@ -60,7 +64,6 @@ public abstract class InternalJadxTest extends TestUtils {
!mthNode.contains(AFlag.INCONSISTENT_CODE) && !mthNode.contains(AType.JADX_ERROR));
}
assertThat(cls.getCode().toString(), not(containsString("inconsistent")));
return cls;
}
protected List<IDexTreeVisitor> getPasses() {
@@ -74,6 +77,11 @@ public abstract class InternalJadxTest extends TestUtils {
public boolean isRawCFGOutput() {
return outputCFG;
}
@Override
public boolean isFallbackMode() {
return isFallback;
}
}, new File(outDir));
}
@@ -94,7 +102,7 @@ public abstract class InternalJadxTest extends TestUtils {
File temp = File.createTempFile("jadx-tmp-", System.nanoTime() + ".jar");
JarOutputStream jo = new JarOutputStream(new FileOutputStream(temp));
for (File file : list) {
add(file, path + "/" + file.getName(), jo);
FileUtils.addFileToJar(jo, file, path + "/" + file.getName());
}
jo.close();
if (deleteTmpJar) {
@@ -127,29 +135,6 @@ public abstract class InternalJadxTest extends TestUtils {
return list;
}
private void add(File source, String entryName, JarOutputStream target) throws IOException {
BufferedInputStream in = null;
try {
JarEntry entry = new JarEntry(entryName);
entry.setTime(source.lastModified());
target.putNextEntry(entry);
in = new BufferedInputStream(new FileInputStream(source));
byte[] buffer = new byte[1024];
while (true) {
int count = in.read(buffer);
if (count == -1) {
break;
}
target.write(buffer, 0, count);
}
target.closeEntry();
} finally {
if (in != null) {
in.close();
}
}
}
// Use only for debug purpose
@Deprecated
@@ -157,6 +142,12 @@ public abstract class InternalJadxTest extends TestUtils {
this.outputCFG = true;
}
// Use only for debug purpose
@Deprecated
protected void setFallback() {
this.isFallback = true;
}
// Use only for debug purpose
@Deprecated
protected void notDeleteTmpJar() {
@@ -5,6 +5,9 @@ import jadx.core.codegen.CodeWriter;
public class TestUtils {
public static String indent(int indent) {
if (indent == 1) {
return CodeWriter.INDENT;
}
StringBuilder sb = new StringBuilder(indent * CodeWriter.INDENT.length());
for (int i = 0; i < indent; i++) {
sb.append(CodeWriter.INDENT);
@@ -25,7 +25,7 @@ public class TestFloatValue extends InternalJadxTest {
String code = cls.getCode().toString();
assertThat(code, not(containsString("1073741824")));
assertThat(code, containsString("0.55f;"));
assertThat(code, containsString("0.55f"));
assertThat(code, containsString("fa[0] = fa[0] / 2.0f;"));
}
}
@@ -5,6 +5,7 @@ import jadx.core.dex.nodes.ClassNode;
import org.junit.Test;
import static jadx.tests.utils.JadxMatchers.containsOne;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.not;
import static org.junit.Assert.assertThat;
@@ -52,13 +53,14 @@ public class TestAnnotations extends InternalJadxTest {
System.out.println(code);
assertThat(code, not(containsString("@A(a = 255)")));
assertThat(code, containsString("@A(a = -1)"));
assertThat(code, containsString("@A(a = -253)"));
assertThat(code, containsString("@A(a = -11253)"));
assertThat(code, containsString("@V(false)"));
assertThat(code, containsOne("@A(a = -1)"));
assertThat(code, containsOne("@A(a = -253)"));
assertThat(code, containsOne("@A(a = -11253)"));
assertThat(code, containsOne("@V(false)"));
assertThat(code, not(containsString("@D()")));
assertThat(code, containsOne("@D"));
assertThat(code, containsString("int a();"));
assertThat(code, containsString("float value() default 1.1f;"));
assertThat(code, containsOne("int a();"));
assertThat(code, containsOne("float value() default 1.1f;"));
}
}
@@ -0,0 +1,46 @@
package jadx.tests.internal.annotations;
import jadx.api.InternalJadxTest;
import jadx.core.dex.nodes.ClassNode;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.junit.Test;
import static org.hamcrest.CoreMatchers.containsString;
import static org.junit.Assert.assertThat;
public class TestParamAnnotations extends InternalJadxTest {
public static class TestCls {
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public static @interface A {
int i() default 7;
}
void test1(@A int i) {
}
void test2(int i, @A int j) {
}
void test3(@A(i = 5) int i) {
}
}
@Test
public void test() {
ClassNode cls = getClassNode(TestCls.class);
String code = cls.getCode().toString();
System.out.println(code);
assertThat(code, containsString("void test1(@A int i) {"));
assertThat(code, containsString("void test2(int i, @A int j) {"));
assertThat(code, containsString("void test3(@A(i = 5) int i) {"));
}
}
@@ -0,0 +1,48 @@
package jadx.tests.internal.annotations;
import jadx.api.InternalJadxTest;
import jadx.core.dex.nodes.ClassNode;
import org.junit.Test;
import static org.hamcrest.CoreMatchers.containsString;
import static org.junit.Assert.assertThat;
public class TestVarArgAnnotation extends InternalJadxTest {
public static class TestCls {
void test1(int... a) {
}
void test2(int i, Object... a) {
}
void test3(int[] a) {
}
void call() {
test1(1, 2);
test2(3, "1", 7);
test3(new int[]{5, 8});
}
}
@Test
public void test() {
ClassNode cls = getClassNode(TestCls.class);
String code = cls.getCode().toString();
System.out.println(code);
assertThat(code, containsString("void test1(int... a) {"));
assertThat(code, containsString("void test2(int i, Object... a) {"));
// TODO:
assertThat(code, containsString("test1(new int[]{1, 2});"));
assertThat(code, containsString("test2(3, new Object[]{\"1\", Integer.valueOf(7)});"));
// negative case
assertThat(code, containsString("void test3(int[] a) {"));
assertThat(code, containsString("test3(new int[]{5, 8});"));
}
}
@@ -0,0 +1,64 @@
package jadx.tests.internal.arith;
import jadx.api.InternalJadxTest;
import jadx.core.dex.nodes.ClassNode;
import org.junit.Test;
import static jadx.tests.utils.JadxMatchers.containsOne;
import static org.junit.Assert.assertThat;
public class TestSpecialValues extends InternalJadxTest {
public static class TestCls {
public void test() {
shorts(Short.MIN_VALUE, Short.MAX_VALUE);
bytes(Byte.MIN_VALUE, Byte.MAX_VALUE);
ints(Integer.MIN_VALUE, Integer.MAX_VALUE);
longs(Long.MIN_VALUE, Long.MAX_VALUE);
floats(Float.NaN, Float.NEGATIVE_INFINITY, Float.POSITIVE_INFINITY,
Float.MIN_VALUE, Float.MAX_VALUE, Float.MIN_NORMAL);
doubles(Double.NaN, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY,
Double.MIN_VALUE, Double.MAX_VALUE, Double.MIN_NORMAL);
}
private void shorts(short... v) {
}
private void bytes(byte... v) {
}
private void ints(int... v) {
}
private void longs(long... v) {
}
private void floats(float... v) {
}
private void doubles(double... v) {
}
}
@Test
public void test() {
ClassNode cls = getClassNode(TestCls.class);
String code = cls.getCode().toString();
System.out.println(code);
assertThat(code, containsOne("Float.NaN, Float.NEGATIVE_INFINITY, Float.POSITIVE_INFINITY, " +
"Float.MIN_VALUE, Float.MAX_VALUE, Float.MIN_NORMAL"));
assertThat(code, containsOne("Double.NaN, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, " +
"Double.MIN_VALUE, Double.MAX_VALUE, Double.MIN_NORMAL"));
assertThat(code, containsOne("Short.MIN_VALUE, Short.MAX_VALUE"));
assertThat(code, containsOne("Byte.MIN_VALUE, Byte.MAX_VALUE"));
assertThat(code, containsOne("Integer.MIN_VALUE, Integer.MAX_VALUE"));
assertThat(code, containsOne("Long.MIN_VALUE, Long.MAX_VALUE"));
}
}
@@ -0,0 +1,28 @@
package jadx.tests.internal.arrays;
import jadx.api.InternalJadxTest;
import jadx.core.dex.nodes.ClassNode;
import org.junit.Test;
import static org.hamcrest.CoreMatchers.containsString;
import static org.junit.Assert.assertThat;
public class TestArrayFill extends InternalJadxTest {
public static class TestCls {
public String[] method() {
return new String[]{"1", "2", "3"};
}
}
@Test
public void test() {
ClassNode cls = getClassNode(TestCls.class);
String code = cls.getCode().toString();
System.out.println(code);
assertThat(code, containsString("return new String[]{\"1\", \"2\", \"3\"};"));
}
}
@@ -0,0 +1,35 @@
package jadx.tests.internal.arrays;
import jadx.api.InternalJadxTest;
import jadx.core.dex.nodes.ClassNode;
import org.junit.Test;
import static org.hamcrest.CoreMatchers.containsString;
import static org.junit.Assert.assertThat;
public class TestArrayFill2 extends InternalJadxTest {
public static class TestCls {
public int[] test(int a) {
return new int[]{1, a + 1, 2};
}
public int[] test2(int a) {
return new int[]{1, a++, a * 2};
}
}
@Test
public void test() {
ClassNode cls = getClassNode(TestCls.class);
String code = cls.getCode().toString();
System.out.println(code);
assertThat(code, containsString("return new int[]{1, a + 1, 2};"));
// TODO
// assertThat(code, containsString("return new int[]{1, a++, a * 2};"));
}
}
@@ -0,0 +1,45 @@
package jadx.tests.internal.conditions;
import jadx.api.InternalJadxTest;
import jadx.core.dex.nodes.ClassNode;
import org.junit.Test;
import static jadx.tests.utils.JadxMatchers.containsOne;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.not;
import static org.junit.Assert.assertThat;
public class TestConditions10 extends InternalJadxTest {
public static class TestCls {
public void test(boolean a, int b) throws Exception {
if (a || b > 2) {
b++;
}
if (!a || (b >= 0 && b <= 11)) {
System.out.println("1");
} else {
System.out.println("2");
}
System.out.println("3");
}
}
@Test
public void test() {
ClassNode cls = getClassNode(TestCls.class);
String code = cls.getCode().toString();
System.out.println(code);
assertThat(code, not(containsString("return")));
assertThat(code, containsOne("if (a || b > 2) {"));
assertThat(code, containsOne("b++;"));
assertThat(code, containsOne("if (!a || (b >= 0 && b <= 11)) {"));
assertThat(code, containsOne("System.out.println(\"1\");"));
assertThat(code, containsOne("} else {"));
assertThat(code, containsOne("System.out.println(\"2\");"));
assertThat(code, containsOne("System.out.println(\"3\");"));
}
}
@@ -0,0 +1,39 @@
package jadx.tests.internal.conditions;
import jadx.api.InternalJadxTest;
import jadx.core.dex.nodes.ClassNode;
import org.junit.Test;
import static jadx.tests.utils.JadxMatchers.containsOne;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.not;
import static org.junit.Assert.assertThat;
public class TestConditions11 extends InternalJadxTest {
public static class TestCls {
public void test(boolean a, int b) {
if (a || b > 2) {
f();
}
}
private void f() {
}
}
@Test
public void test() {
ClassNode cls = getClassNode(TestCls.class);
String code = cls.getCode().toString();
System.out.println(code);
assertThat(code, containsOne("if (a || b > 2) {"));
assertThat(code, containsOne("f();"));
assertThat(code, not(containsString("return")));
assertThat(code, not(containsString("else")));
}
}
@@ -0,0 +1,71 @@
package jadx.tests.internal.conditions;
import jadx.api.InternalJadxTest;
import jadx.core.dex.nodes.ClassNode;
import org.junit.Test;
import static jadx.tests.utils.JadxMatchers.containsOne;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.not;
import static org.junit.Assert.assertThat;
public class TestConditions12 extends InternalJadxTest {
public static class TestCls {
static boolean autoStop = true;
static boolean qualityReading = false;
static int lastValidRaw = -1;
public static void main(String[] args) throws Exception {
int a = 5;
int b = 30;
dataProcess(a, b);
}
public static void dataProcess(int raw, int quality) {
if (quality >= 10 && raw != 0) {
System.out.println("OK" + raw);
qualityReading = false;
} else if (raw == 0 || quality < 6 || !qualityReading) {
System.out.println("Not OK" + raw);
} else {
System.out.println("Quit OK" + raw);
}
if (quality < 30) {
int timeLeft = 30 - quality;
if (quality >= 10) {
System.out.println("Processing" + timeLeft);
}
} else {
System.out.println("Finish Processing");
if (raw > 0) {
lastValidRaw = raw;
}
}
if (quality >= 30 && autoStop) {
System.out.println("Finished");
}
if (!autoStop && lastValidRaw > -1 && quality < 10) {
System.out.println("Finished");
}
}
}
@Test
public void test() {
ClassNode cls = getClassNode(TestCls.class);
String code = cls.getCode().toString();
System.out.println(code);
assertThat(code, containsOne("if (quality >= 10 && raw != 0) {"));
assertThat(code, containsOne("} else if (raw == 0 || quality < 6 || !qualityReading) {"));
assertThat(code, containsOne("if (quality < 30) {"));
assertThat(code, containsOne("if (quality >= 10) {"));
assertThat(code, containsOne("if (raw > 0) {"));
assertThat(code, containsOne("if (quality >= 30 && autoStop) {"));
assertThat(code, containsOne("if (!autoStop && lastValidRaw > -1 && quality < 10) {"));
assertThat(code, not(containsString("return")));
}
}
@@ -0,0 +1,43 @@
package jadx.tests.internal.conditions;
import jadx.api.InternalJadxTest;
import jadx.core.dex.nodes.ClassNode;
import org.junit.Test;
import static jadx.tests.utils.JadxMatchers.containsOne;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.not;
import static org.junit.Assert.assertThat;
public class TestConditions13 extends InternalJadxTest {
public static class TestCls {
static boolean qualityReading;
public static void dataProcess(int raw, int quality) {
if (quality >= 10 && raw != 0) {
System.out.println("OK" + raw);
qualityReading = false;
} else if (raw == 0 || quality < 6 || !qualityReading) {
System.out.println("Not OK" + raw);
} else {
System.out.println("Quit OK" + raw);
}
}
}
@Test
public void test() {
ClassNode cls = getClassNode(TestCls.class);
String code = cls.getCode().toString();
System.out.println(code);
assertThat(code, containsOne("if (quality >= 10 && raw != 0) {"));
assertThat(code, containsOne("System.out.println(\"OK\" + raw);"));
assertThat(code, containsOne("qualityReading = false;"));
assertThat(code, containsOne("} else if (raw == 0 || quality < 6 || !qualityReading) {"));
assertThat(code, not(containsString("return")));
}
}
@@ -8,7 +8,6 @@ import org.junit.Test;
public class TestConditions2 extends InternalJadxTest {
public static class TestCls {
int c;
String d;
String f;
@@ -21,6 +21,18 @@ public class TestConditions5 extends InternalJadxTest {
throw new AssertionError(a1 + " != " + a2);
}
}
public static void assertEquals2(Object a1, Object a2) {
if (a1 != null) {
if (!a1.equals(a2)) {
throw new AssertionError(a1 + " != " + a2);
}
} else {
if (a2 != null) {
throw new AssertionError(a1 + " != " + a2);
}
}
}
}
@Test
@@ -0,0 +1,37 @@
package jadx.tests.internal.conditions;
import jadx.api.InternalJadxTest;
import jadx.core.dex.nodes.ClassNode;
import org.junit.Test;
import static jadx.tests.utils.JadxMatchers.containsOne;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.not;
import static org.junit.Assert.assertThat;
public class TestConditions9 extends InternalJadxTest {
public static class TestCls {
public void test(boolean a, int b) throws Exception {
if (!a || (b >= 0 && b <= 11)) {
System.out.println('1');
} else {
System.out.println('2');
}
}
}
@Test
public void test() {
ClassNode cls = getClassNode(TestCls.class);
String code = cls.getCode().toString();
System.out.println(code);
assertThat(code, containsOne("if (!a || (b >= 0 && b <= 11)) {"));
assertThat(code, containsOne("System.out.println('1');"));
assertThat(code, containsOne("} else {"));
assertThat(code, containsOne("System.out.println('2');"));
assertThat(code, not(containsString("return;")));
}
}
@@ -0,0 +1,32 @@
package jadx.tests.internal.conditions;
import jadx.api.InternalJadxTest;
import jadx.core.dex.nodes.ClassNode;
import org.junit.Test;
import static org.hamcrest.CoreMatchers.containsString;
import static org.junit.Assert.assertThat;
public class TestSimpleConditions extends InternalJadxTest {
public static class TestCls {
public boolean test1(boolean[] a) {
return (a[0] && a[1] && a[2]) || (a[3] && a[4]);
}
public boolean test2(boolean[] a) {
return a[0] || a[1] || a[2] || a[3];
}
}
@Test
public void test() {
ClassNode cls = getClassNode(TestCls.class);
String code = cls.getCode().toString();
System.out.println(code);
assertThat(code, containsString("return (a[0] && a[1] && a[2]) || (a[3] && a[4]);"));
assertThat(code, containsString("return a[0] || a[1] || a[2] || a[3];"));
}
}
@@ -0,0 +1,71 @@
package jadx.tests.internal.enums;
import jadx.api.InternalJadxTest;
import jadx.core.dex.nodes.ClassNode;
import org.junit.Test;
import static jadx.tests.utils.JadxMatchers.containsLines;
import static org.junit.Assert.assertThat;
public class TestEnums extends InternalJadxTest {
public static class TestCls {
public enum EmptyEnum {
}
public enum EmptyEnum2 {
;
public static void mth() {
}
}
public enum Direction {
NORTH,
SOUTH,
EAST,
WEST
}
public enum Singleton {
INSTANCE;
public String test() {
return "";
}
}
}
@Test
public void test() {
ClassNode cls = getClassNode(TestCls.class);
String code = cls.getCode().toString();
System.out.println(code);
assertThat(code, containsLines(1, "public enum EmptyEnum {", "}"));
assertThat(code, containsLines(1,
"public enum EmptyEnum2 {",
indent(1) + ";",
"",
indent(1) + "public static void mth() {",
indent(1) + "}",
"}"));
assertThat(code, containsLines(1, "public enum Direction {",
indent(1) + "NORTH,",
indent(1) + "SOUTH,",
indent(1) + "EAST,",
indent(1) + "WEST",
"}"));
assertThat(code, containsLines(1, "public enum Singleton {",
indent(1) + "INSTANCE;",
"",
indent(1) + "public String test() {",
indent(2) + "return \"\";",
indent(1) + "}",
"}"));
}
}
@@ -0,0 +1,54 @@
package jadx.tests.internal.enums;
import jadx.api.InternalJadxTest;
import jadx.core.dex.nodes.ClassNode;
import jadx.tests.utils.JadxMatchers;
import org.junit.Test;
import static org.junit.Assert.assertThat;
public class TestEnums2 extends InternalJadxTest {
public static class TestCls {
public enum Operation {
PLUS {
int apply(int x, int y) {
return x + y;
}
},
MINUS {
int apply(int x, int y) {
return x - y;
}
};
abstract int apply(int x, int y);
}
}
@Test
public void test() {
ClassNode cls = getClassNode(TestCls.class);
String code = cls.getCode().toString();
System.out.println(code);
assertThat(code, JadxMatchers.containsLines(1,
"public enum Operation {",
indent(1) + "PLUS {",
indent(2) + "int apply(int x, int y) {",
indent(3) + "return x + y;",
indent(2) + "}",
indent(1) + "},",
indent(1) + "MINUS {",
indent(2) + "int apply(int x, int y) {",
indent(3) + "return x - y;",
indent(2) + "}",
indent(1) + "};",
"",
indent(1) + "abstract int apply(int i, int i2);",
"}"
));
}
}
@@ -0,0 +1,48 @@
package jadx.tests.internal.inline;
import jadx.api.InternalJadxTest;
import jadx.core.dex.nodes.ClassNode;
import org.junit.Test;
import static jadx.tests.utils.JadxMatchers.containsOne;
import static jadx.tests.utils.JadxMatchers.countString;
import static org.junit.Assert.assertThat;
public class TestInlineInLoop extends InternalJadxTest {
public static class TestCls {
public static void main(String[] args) throws Exception {
int a = 0;
int b = 4;
int c = 0;
while (a < 12) {
if (b + a < 9 && b < 8) {
if (b >= 2 && a > -1 && b < 6) {
System.out.println("OK");
c = b + 1;
}
c = b;
}
c = b;
b++;
b = c;
a++;
}
}
}
@Test
public void test() {
ClassNode cls = getClassNode(TestCls.class);
String code = cls.getCode().toString();
System.out.println(code);
assertThat(code, containsOne("int c"));
assertThat(code, containsOne("c = b + 1"));
assertThat(code, countString(2, "c = b;"));
assertThat(code, containsOne("b++;"));
assertThat(code, containsOne("b = c"));
assertThat(code, containsOne("a++;"));
}
}
@@ -0,0 +1,48 @@
package jadx.tests.internal.inner;
import jadx.api.InternalJadxTest;
import jadx.core.dex.nodes.ClassNode;
import org.junit.Test;
import static jadx.tests.utils.JadxMatchers.containsOne;
import static jadx.tests.utils.JadxMatchers.countString;
import static org.junit.Assert.assertThat;
public class TestAnonymousClass4 extends InternalJadxTest {
public static class TestCls {
public static class Inner {
private int f;
private double d;
public void test() {
new Thread() {
{
f = 1;
}
@Override
public void run() {
d = 7.5;
}
}.start();
}
}
}
@Test
public void test() {
ClassNode cls = getClassNode(TestCls.class);
String code = cls.getCode().toString();
System.out.println(code);
assertThat(code, containsOne(indent(3) + "new Thread() {"));
assertThat(code, containsOne(indent(4) + "{"));
assertThat(code, containsOne("f = 1;"));
assertThat(code, countString(2, indent(4) + "}"));
assertThat(code, containsOne(indent(4) + "public void run() {"));
assertThat(code, containsOne("d = 7.5"));
assertThat(code, containsOne(indent(3) + "}.start();"));
}
}
@@ -5,8 +5,7 @@ import jadx.core.dex.nodes.ClassNode;
import org.junit.Test;
import static org.hamcrest.CoreMatchers.containsString;
import static org.junit.Assert.assertEquals;
import static jadx.tests.utils.JadxMatchers.containsOne;
import static org.junit.Assert.assertThat;
public class TestBreakInLoop extends InternalJadxTest {
@@ -33,8 +32,12 @@ public class TestBreakInLoop extends InternalJadxTest {
String code = cls.getCode().toString();
System.out.println(code);
assertEquals(1, count(code, "this.f++;"));
assertThat(code, containsString("if (i < b) {"));
assertThat(code, containsString("break;"));
assertThat(code, containsOne("this.f++;"));
// assertThat(code, containsOne("a[i]++;"));
assertThat(code, containsOne("if (i < b) {"));
assertThat(code, containsOne("break;"));
assertThat(code, containsOne("i++;"));
// assertThat(code, countString(0, "else"));
}
}
@@ -5,7 +5,7 @@ import jadx.core.dex.nodes.ClassNode;
import org.junit.Test;
import static org.hamcrest.CoreMatchers.containsString;
import static jadx.tests.utils.JadxMatchers.containsOne;
import static org.junit.Assert.assertThat;
public class TestLoopCondition extends InternalJadxTest {
@@ -48,8 +48,12 @@ public class TestLoopCondition extends InternalJadxTest {
String code = cls.getCode().toString();
System.out.println(code);
assertThat(code, containsString("i < this.f.length()"));
assertThat(code, containsString("list.set(i, \"ABC\")"));
assertThat(code, containsString("list.set(i, \"DEF\")"));
assertThat(code, containsOne("i < this.f.length()"));
assertThat(code, containsOne("list.set(i, \"ABC\")"));
assertThat(code, containsOne("list.set(i, \"DEF\")"));
assertThat(code, containsOne("if (j == 2) {"));
assertThat(code, containsOne("setEnabled(true);"));
assertThat(code, containsOne("setEnabled(false);"));
}
}
@@ -5,7 +5,7 @@ import jadx.core.dex.nodes.ClassNode;
import org.junit.Test;
import static org.hamcrest.CoreMatchers.containsString;
import static jadx.tests.utils.JadxMatchers.containsOne;
import static org.junit.Assert.assertThat;
public class TestLoopCondition2 extends InternalJadxTest {
@@ -27,6 +27,9 @@ public class TestLoopCondition2 extends InternalJadxTest {
String code = cls.getCode().toString();
System.out.println(code);
assertThat(code, containsString("while (a && i < 10) {"));
assertThat(code, containsOne("int i = 0;"));
assertThat(code, containsOne("while (a && i < 10) {"));
assertThat(code, containsOne("i++;"));
assertThat(code, containsOne("return i;"));
}
}
@@ -0,0 +1,42 @@
package jadx.tests.internal.loops;
import jadx.api.InternalJadxTest;
import jadx.core.dex.nodes.ClassNode;
import org.junit.Test;
import static jadx.tests.utils.JadxMatchers.containsOne;
import static org.junit.Assert.assertThat;
public class TestLoopCondition3 extends InternalJadxTest {
public static class TestCls {
public static void test(int a, int b, int c) {
while (a < 12) {
if (b + a < 9 && b < 8) {
if (b >= 2 && a > -1 && b < 6) {
System.out.println("OK");
c = b + 1;
}
b = a;
}
c = b;
b++;
b = c;
a++;
}
}
}
@Test
public void test() {
ClassNode cls = getClassNode(TestCls.class);
String code = cls.getCode().toString();
System.out.println(code);
assertThat(code, containsOne("while (a < 12) {"));
assertThat(code, containsOne("if (b + a < 9 && b < 8) {"));
assertThat(code, containsOne("if (b >= 2 && a > -1 && b < 6) {"));
}
}
@@ -0,0 +1,39 @@
package jadx.tests.internal.loops;
import jadx.api.InternalJadxTest;
import jadx.core.dex.nodes.ClassNode;
import org.junit.Test;
import static jadx.tests.utils.JadxMatchers.containsOne;
import static org.junit.Assert.assertThat;
public class TestLoopCondition4 extends InternalJadxTest {
public static class TestCls {
public static void test() {
int n = -1;
while (n < 0) {
n += 12;
}
while (n > 11) {
n -= 12;
}
System.out.println(n);
}
}
@Test
public void test() {
ClassNode cls = getClassNode(TestCls.class);
String code = cls.getCode().toString();
System.out.println(code);
assertThat(code, containsOne("int n = -1;"));
assertThat(code, containsOne("while (n < 0) {"));
assertThat(code, containsOne("n += 12;"));
assertThat(code, containsOne("while (n > 11) {"));
assertThat(code, containsOne("n -= 12;"));
assertThat(code, containsOne("System.out.println(n);"));
}
}
@@ -0,0 +1,47 @@
package jadx.tests.internal.loops;
import jadx.api.InternalJadxTest;
import jadx.core.dex.nodes.ClassNode;
import org.junit.Test;
import static jadx.tests.utils.JadxMatchers.containsOne;
import static org.junit.Assert.assertThat;
public class TestLoopConditionInvoke extends InternalJadxTest {
public static class TestCls {
private static final char STOP_CHAR = 0;
private int pos;
private boolean test(char lastChar) {
int startPos = pos;
char ch;
while ((ch = next()) != STOP_CHAR) {
if (ch == lastChar) {
return true;
}
}
pos = startPos;
return false;
}
private char next() {
return 0;
}
}
@Test
public void test() {
ClassNode cls = getClassNode(TestCls.class);
String code = cls.getCode().toString();
System.out.println(code);
assertThat(code, containsOne("do {"));
assertThat(code, containsOne("if (ch == '\\u0000') {"));
assertThat(code, containsOne("this.pos = startPos;"));
assertThat(code, containsOne("return false;"));
assertThat(code, containsOne("} while (ch != lastChar);"));
assertThat(code, containsOne("return true;"));
}
}
@@ -0,0 +1,47 @@
package jadx.tests.internal.loops;
import jadx.api.InternalJadxTest;
import jadx.core.dex.nodes.ClassNode;
import org.junit.Test;
import static jadx.tests.utils.JadxMatchers.containsOne;
import static jadx.tests.utils.JadxMatchers.countString;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.not;
import static org.junit.Assert.assertThat;
public class TestSequentialLoops extends InternalJadxTest {
public static class TestCls {
public int test7(int a, int b) {
int c = b;
int z;
while (true) {
z = c + a;
if (z >= 7) {
break;
}
c = z;
}
while ((z = c + a) >= 7) {
c = z;
}
return c;
}
}
@Test
public void test() {
ClassNode cls = getClassNode(TestCls.class);
String code = cls.getCode().toString();
System.out.println(code);
assertThat(code, countString(2, "while ("));
assertThat(code, containsOne("break;"));
assertThat(code, containsOne("return c;"));
assertThat(code, not(containsString("else")));
}
}
@@ -0,0 +1,56 @@
package jadx.tests.internal.others;
import jadx.api.InternalJadxTest;
import jadx.core.dex.nodes.ClassNode;
import java.io.File;
import java.io.IOException;
import org.junit.Test;
import static jadx.tests.utils.JadxMatchers.containsOne;
import static jadx.tests.utils.JadxMatchers.countString;
import static org.junit.Assert.assertThat;
public class TestIfInTry extends InternalJadxTest {
public static class TestCls {
private File dir;
public int test() {
try {
int a = f();
if (a != 0) {
return a;
}
} catch (Exception e) {
// skip
}
try {
f();
return 1;
} catch (IOException e) {
return -1;
}
}
private int f() throws IOException {
return 0;
}
}
@Test
public void test() {
ClassNode cls = getClassNode(TestCls.class);
String code = cls.getCode().toString();
System.out.println(code);
assertThat(code, containsOne("if (a != 0) {"));
assertThat(code, containsOne("} catch (Exception e) {"));
assertThat(code, countString(2, "try {"));
assertThat(code, countString(3, "f()"));
assertThat(code, containsOne("return 1;"));
assertThat(code, containsOne("} catch (IOException e"));
assertThat(code, containsOne("return -1;"));
}
}

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