Compare commits
40 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 671be0af0a | |||
| 7e9278f992 | |||
| 9194441c47 | |||
| 4f307c0085 | |||
| 3bdda55102 | |||
| b657b0fb1f | |||
| 4935ae6da5 | |||
| 72a50eae43 | |||
| fa37b90cff | |||
| 052a8db606 | |||
| 88ccba166e | |||
| 58998089a6 | |||
| f0a73b329e | |||
| c97678a477 | |||
| 2ad739275f | |||
| caad78885d | |||
| a234227b9f | |||
| 16f736e773 | |||
| 1fe24ad11d | |||
| 33c5e0827a | |||
| cbd36aeb8f | |||
| 2963bb3f41 | |||
| 09a6ceac63 | |||
| 75d8a01cab | |||
| 0968f75e9a | |||
| bc0db88afa | |||
| 5f11f12d0c | |||
| 2d18950542 | |||
| 50d314445a | |||
| f8d57d9265 | |||
| ebbe6db378 | |||
| 543cad3a23 | |||
| 41cc83dbf6 | |||
| ce7101be88 | |||
| 0a241e3a9c | |||
| 37857e88ea | |||
| 6fbcf46a8b | |||
| a36bc8f29a | |||
| 813b7bca6e | |||
| e2945f2a42 |
+6
-4
@@ -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:
|
||||
|
||||
@@ -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
|
||||
===================
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
Command line and GUI tools for produce Java source code from Android Dex and Apk files
|
||||
|
||||

|
||||
|
||||
### 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
@@ -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'
|
||||
}
|
||||
|
||||
Vendored
BIN
Binary file not shown.
+1
-1
@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=http\://services.gradle.org/distributions/gradle-1.12-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-2.0-bin.zip
|
||||
|
||||
@@ -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 '../.'
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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) {
|
||||
|
||||
+40
-39
@@ -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;
|
||||
}
|
||||
|
||||
+1
-1
@@ -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
Reference in New Issue
Block a user