Compare commits
104 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fcb120a3ed | |||
| 988628a2e7 | |||
| c24cdf5cc1 | |||
| d748e004d2 | |||
| 380b73d1b9 | |||
| ef85e29a9b | |||
| 1daf5d1090 | |||
| 9d2c0e4aea | |||
| 7277ebb9c4 | |||
| c18074f6aa | |||
| 8a706193e7 | |||
| 9d77f5f5df | |||
| 6951d0e646 | |||
| 73dd55eac2 | |||
| b5a9389cc6 | |||
| d905c96fbe | |||
| 03f03f85af | |||
| 2b00a8a406 | |||
| f31c2dcd21 | |||
| 7699cfac02 | |||
| 5c48a457b4 | |||
| b5f439e1aa | |||
| 202fe5a0a9 | |||
| 68ccf57bd4 | |||
| 84970759d8 | |||
| 53cac58ebe | |||
| adc32ed319 | |||
| 7f0815a7b2 | |||
| 68f5565b63 | |||
| c552fb857d | |||
| 8a4ec47b92 | |||
| d281126337 | |||
| 4fb6ada5ec | |||
| ab924faa1e | |||
| b12b129af7 | |||
| 017c6b4d42 | |||
| d55cd5fbb4 | |||
| 13a6b1c8c6 | |||
| 0bc37e5d32 | |||
| 46c8572887 | |||
| e6b919007c | |||
| ac5a6096bb | |||
| db527fbbda | |||
| 8f201f1fee | |||
| d1e0762c12 | |||
| 010ae99c69 | |||
| a4632d6e86 | |||
| 2a3162f869 | |||
| 2063fd0742 | |||
| 128fe8a839 | |||
| 2478fc3a1b | |||
| 5a68d3bef7 | |||
| 195eeceb62 | |||
| ec8309af49 | |||
| 627a4dc802 | |||
| e2018535ef | |||
| ee56610f06 | |||
| fb9ff7748a | |||
| cdfb46d9d3 | |||
| 5545a94a9e | |||
| 9e811d959b | |||
| 957d5394d2 | |||
| 95afe1219e | |||
| 07937f1d71 | |||
| 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 |
+7
-5
@@ -1,19 +1,21 @@
|
||||
language: java
|
||||
jdk:
|
||||
- oraclejdk8
|
||||
- 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,30 @@ 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"`
|
||||
|
||||
|
||||
### Contribution
|
||||
|
||||
To support this project you can:
|
||||
- Post thoughts about new features/optimizations that important to you
|
||||
- Submit bug using one of following patterns:
|
||||
* Java code examples which decompiles incorrectly
|
||||
* Error log and link to _public available_ apk file or app page on Google play
|
||||
|
||||
And any other comments will be very helpfull,
|
||||
because at current stage of development it is very time consuming
|
||||
to **find** new bugs, design and implement new features.
|
||||
Also I need to **prioritize** these task for complete most important at first.
|
||||
|
||||
---------------------------------------
|
||||
*Licensed under the Apache 2.0 License*
|
||||
|
||||
*Copyright 2014 by Skylot*
|
||||
|
||||
+4
-3
@@ -32,13 +32,14 @@ subprojects {
|
||||
|
||||
testCompile 'ch.qos.logback:logback-classic:1.1.2'
|
||||
testCompile 'junit:junit:4.11'
|
||||
testCompile 'org.mockito:mockito-core:1.9.5'
|
||||
testCompile 'org.mockito:mockito-core:1.10.10'
|
||||
testCompile 'org.spockframework:spock-core:0.7-groovy-2.0'
|
||||
testCompile 'cglib:cglib-nodep:3.1'
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
jcenter()
|
||||
}
|
||||
|
||||
jacocoTestReport {
|
||||
@@ -56,7 +57,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 +89,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")
|
||||
@@ -29,9 +29,12 @@ public final class JadxCLIArgs implements IJadxArgs {
|
||||
@Parameter(names = {"-j", "--threads-count"}, description = "processing threads count")
|
||||
protected int threadsCount = Runtime.getRuntime().availableProcessors();
|
||||
|
||||
@Parameter(names = {"-f", "--fallback"}, description = "make simple dump (using goto instead of 'if', 'for', etc)", help = true)
|
||||
@Parameter(names = {"-f", "--fallback"}, description = "make simple dump (using goto instead of 'if', 'for', etc)")
|
||||
protected boolean fallbackMode = false;
|
||||
|
||||
@Parameter(names = {"--show-bad-code"}, description = "show inconsistent code (incorrectly decompiled)")
|
||||
protected boolean showInconsistentCode = false;
|
||||
|
||||
@Parameter(names = {"--cfg"}, description = "save methods control flow graph to dot file")
|
||||
protected boolean cfgOutput = false;
|
||||
|
||||
@@ -105,7 +108,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 +151,7 @@ public final class JadxCLIArgs implements IJadxArgs {
|
||||
return input;
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getOutDir() {
|
||||
return outputDir;
|
||||
}
|
||||
@@ -180,6 +184,11 @@ public final class JadxCLIArgs implements IJadxArgs {
|
||||
return fallbackMode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isShowInconsistentCode() {
|
||||
return showInconsistentCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVerbose() {
|
||||
return verbose;
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
ext.jadxClasspath = 'clsp-data/android-4.3.jar'
|
||||
|
||||
dependencies {
|
||||
compile files('lib/dx-1.8.jar')
|
||||
runtime files(jadxClasspath)
|
||||
|
||||
compile files('lib/dx-1.10.jar')
|
||||
compile 'org.ow2.asm:asm:5.0.3'
|
||||
compile 'com.intellij:annotations:12.0'
|
||||
|
||||
testCompile 'org.smali:smali:2.0.3'
|
||||
}
|
||||
|
||||
task packTests(type: Jar) {
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -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();
|
||||
@@ -22,6 +29,11 @@ public class DefaultJadxArgs implements IJadxArgs {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isShowInconsistentCode() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVerbose() {
|
||||
return false;
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
package jadx.api;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public interface IJadxArgs {
|
||||
File getOutDir();
|
||||
|
||||
int getThreadsCount();
|
||||
|
||||
boolean isCFGOutput();
|
||||
@@ -9,5 +13,7 @@ public interface IJadxArgs {
|
||||
|
||||
boolean isFallbackMode();
|
||||
|
||||
boolean isShowInconsistentCode();
|
||||
|
||||
boolean isVerbose();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,28 +116,31 @@ public final class JavaClass implements JavaNode {
|
||||
public CodePosition getDefinitionPosition(int line, int offset) {
|
||||
Map<CodePosition, Object> map = getCodeAnnotations();
|
||||
Object obj = map.get(new CodePosition(line, offset));
|
||||
if (obj instanceof LineAttrNode) {
|
||||
ClassNode clsNode = null;
|
||||
if (obj instanceof ClassNode) {
|
||||
clsNode = (ClassNode) obj;
|
||||
} else if (obj instanceof MethodNode) {
|
||||
clsNode = ((MethodNode) obj).getParentClass();
|
||||
} else if (obj instanceof FieldNode) {
|
||||
clsNode = ((FieldNode) obj).getParentClass();
|
||||
}
|
||||
if (clsNode == null) {
|
||||
return null;
|
||||
}
|
||||
clsNode = clsNode.getParentClass();
|
||||
JavaClass jCls = decompiler.findJavaClass(clsNode);
|
||||
if (jCls == null) {
|
||||
return null;
|
||||
}
|
||||
jCls.decompile();
|
||||
int defLine = ((LineAttrNode) obj).getDecompiledLine();
|
||||
return new CodePosition(jCls, defLine, 0);
|
||||
if (!(obj instanceof LineAttrNode)) {
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
ClassNode clsNode = null;
|
||||
if (obj instanceof ClassNode) {
|
||||
clsNode = (ClassNode) obj;
|
||||
} else if (obj instanceof MethodNode) {
|
||||
clsNode = ((MethodNode) obj).getParentClass();
|
||||
} else if (obj instanceof FieldNode) {
|
||||
clsNode = ((FieldNode) obj).getParentClass();
|
||||
}
|
||||
if (clsNode == null) {
|
||||
return null;
|
||||
}
|
||||
clsNode = clsNode.getTopParentClass();
|
||||
JavaClass jCls = decompiler.findJavaClass(clsNode);
|
||||
if (jCls == null) {
|
||||
return null;
|
||||
}
|
||||
jCls.decompile();
|
||||
int defLine = ((LineAttrNode) obj).getDecompiledLine();
|
||||
if (defLine == 0) {
|
||||
return null;
|
||||
}
|
||||
return new CodePosition(jCls, defLine, 0);
|
||||
}
|
||||
|
||||
public Integer getSourceLine(int decompiledLine) {
|
||||
|
||||
@@ -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,9 +14,11 @@ 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;
|
||||
import jadx.core.dex.visitors.regions.LoopRegionVisitor;
|
||||
import jadx.core.dex.visitors.regions.ProcessVariables;
|
||||
import jadx.core.dex.visitors.regions.RegionMakerVisitor;
|
||||
import jadx.core.dex.visitors.regions.ReturnVisitor;
|
||||
@@ -57,40 +59,40 @@ public class Jadx {
|
||||
passes.add(new SSATransform());
|
||||
passes.add(new DebugInfoVisitor());
|
||||
passes.add(new TypeInference());
|
||||
if (args.isRawCFGOutput()) {
|
||||
passes.add(DotGraphVisitor.dumpRaw(outDir));
|
||||
}
|
||||
|
||||
passes.add(new ConstInlinerVisitor());
|
||||
passes.add(new FinishTypeInference());
|
||||
|
||||
if (args.isRawCFGOutput()) {
|
||||
passes.add(new DotGraphVisitor(outDir, false, true));
|
||||
}
|
||||
|
||||
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(DotGraphVisitor.dump(outDir));
|
||||
}
|
||||
|
||||
passes.add(new RegionMakerVisitor());
|
||||
passes.add(new IfRegionVisitor());
|
||||
passes.add(new ReturnVisitor());
|
||||
|
||||
passes.add(new CodeShrinker());
|
||||
passes.add(new SimplifyVisitor());
|
||||
passes.add(new ProcessVariables());
|
||||
passes.add(new CheckRegions());
|
||||
|
||||
if (args.isCFGOutput()) {
|
||||
passes.add(new DotGraphVisitor(outDir, true));
|
||||
passes.add(DotGraphVisitor.dumpRegions(outDir));
|
||||
}
|
||||
|
||||
passes.add(new MethodInlineVisitor());
|
||||
passes.add(new ClassModifier());
|
||||
passes.add(new PrepareForCodeGen());
|
||||
|
||||
if (args.isCFGOutput()) {
|
||||
passes.add(new DotGraphVisitor(outDir, false));
|
||||
}
|
||||
passes.add(new LoopRegionVisitor());
|
||||
passes.add(new ProcessVariables());
|
||||
}
|
||||
passes.add(new CodeGen(args));
|
||||
return passes;
|
||||
|
||||
@@ -22,7 +22,7 @@ public final class ProcessClass {
|
||||
DepthTraversal.visit(visitor, cls);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.error("Class process exception: " + cls, e);
|
||||
LOG.error("Class process exception: {}", cls, e);
|
||||
} finally {
|
||||
cls.unload();
|
||||
}
|
||||
|
||||
@@ -3,9 +3,9 @@ package jadx.core.clsp;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.DecodeException;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
import jadx.core.utils.files.FileUtils;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
@@ -96,13 +96,13 @@ public class ClsSet {
|
||||
private static NClass getCls(String fullName, Map<String, NClass> names) {
|
||||
NClass id = names.get(fullName);
|
||||
if (id == null && !names.containsKey(fullName)) {
|
||||
LOG.warn("Class not found: " + fullName);
|
||||
LOG.warn("Class not found: {}", fullName);
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
void save(File output) throws IOException {
|
||||
Utils.makeDirsForFile(output);
|
||||
FileUtils.makeDirsForFile(output);
|
||||
|
||||
BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(output));
|
||||
try {
|
||||
@@ -132,7 +132,7 @@ public class ClsSet {
|
||||
out.writeBytes(JADX_CLS_SET_HEADER);
|
||||
out.writeByte(VERSION);
|
||||
|
||||
LOG.info("Classes count: " + classes.length);
|
||||
LOG.info("Classes count: {}", classes.length);
|
||||
out.writeInt(classes.length);
|
||||
for (NClass cls : classes) {
|
||||
writeString(out, cls.getName());
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -39,7 +39,7 @@ public class ConvertToClsSet {
|
||||
}
|
||||
}
|
||||
for (InputFile inputFile : inputFiles) {
|
||||
LOG.info("Loaded: " + inputFile.getFile());
|
||||
LOG.info("Loaded: {}", inputFile.getFile());
|
||||
}
|
||||
|
||||
RootNode root = new RootNode();
|
||||
@@ -48,7 +48,7 @@ public class ConvertToClsSet {
|
||||
ClsSet set = new ClsSet();
|
||||
set.load(root);
|
||||
set.save(output);
|
||||
LOG.info("Output: " + output);
|
||||
LOG.info("Output: {}", output);
|
||||
LOG.info("done");
|
||||
}
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ public class AnnotationGen {
|
||||
|
||||
public void addForParameter(CodeWriter code, MethodParameters paramsAnnotations, int n) {
|
||||
AnnotationsList aList = paramsAnnotations.getParamList().get(n);
|
||||
if (aList == null || aList.size() == 0) {
|
||||
if (aList == null || aList.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
for (Annotation a : aList.getAll()) {
|
||||
@@ -54,7 +54,7 @@ public class AnnotationGen {
|
||||
|
||||
private void add(IAttributeNode node, CodeWriter code) {
|
||||
AnnotationsList aList = node.get(AType.ANNOTATION_LIST);
|
||||
if (aList == null || aList.size() == 0) {
|
||||
if (aList == null || aList.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
for (Annotation a : aList.getAll()) {
|
||||
@@ -150,9 +150,9 @@ public class AnnotationGen {
|
||||
// must be a static field
|
||||
FieldInfo field = (FieldInfo) val;
|
||||
InsnGen.makeStaticFieldAccess(code, field, classGen);
|
||||
} else if (val instanceof List) {
|
||||
} else if (val instanceof Iterable) {
|
||||
code.add('{');
|
||||
Iterator<?> it = ((List) val).iterator();
|
||||
Iterator<?> it = ((Iterable) val).iterator();
|
||||
while (it.hasNext()) {
|
||||
Object obj = it.next();
|
||||
encodeValue(code, obj);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package jadx.core.codegen;
|
||||
|
||||
import jadx.api.IJadxArgs;
|
||||
import jadx.core.Consts;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
@@ -23,6 +24,7 @@ import jadx.core.utils.exceptions.CodegenException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
@@ -38,13 +40,27 @@ import com.android.dx.rop.code.AccessFlags;
|
||||
public class ClassGen {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ClassGen.class);
|
||||
|
||||
public static final Comparator<MethodNode> METHOD_LINE_COMPARATOR = new Comparator<MethodNode>() {
|
||||
@Override
|
||||
public int compare(MethodNode a, MethodNode b) {
|
||||
return Utils.compare(a.getSourceLine(), b.getSourceLine());
|
||||
}
|
||||
};
|
||||
|
||||
private final ClassNode cls;
|
||||
private final ClassGen parentGen;
|
||||
private final AnnotationGen annotationGen;
|
||||
private final boolean fallback;
|
||||
|
||||
private boolean showInconsistentCode = false;
|
||||
|
||||
private final Set<ClassInfo> imports = new HashSet<ClassInfo>();
|
||||
private int clsDeclLine = 0;
|
||||
private int clsDeclLine;
|
||||
|
||||
public ClassGen(ClassNode cls, ClassGen parentClsGen, IJadxArgs jadxArgs) {
|
||||
this(cls, parentClsGen, jadxArgs.isFallbackMode());
|
||||
this.showInconsistentCode = jadxArgs.isShowInconsistentCode();
|
||||
}
|
||||
|
||||
public ClassGen(ClassNode cls, ClassGen parentClsGen, boolean fallback) {
|
||||
this.cls = cls;
|
||||
@@ -103,7 +119,14 @@ 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);
|
||||
}
|
||||
|
||||
// 'static' modifier not allowed for top classes (not inner)
|
||||
if (!cls.getClassInfo().isInner()) {
|
||||
af = af.remove(AccessFlags.ACC_STATIC);
|
||||
}
|
||||
|
||||
annotationGen.addForClass(clsCode);
|
||||
@@ -133,7 +156,7 @@ public class ClassGen {
|
||||
clsCode.add(' ');
|
||||
}
|
||||
|
||||
if (cls.getInterfaces().size() > 0 && !af.isAnnotation()) {
|
||||
if (!cls.getInterfaces().isEmpty() && !af.isAnnotation()) {
|
||||
if (cls.getAccessFlags().isInterface()) {
|
||||
clsCode.add("extends ");
|
||||
} else {
|
||||
@@ -202,35 +225,63 @@ 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) + " */");
|
||||
}
|
||||
List<MethodNode> methods = sortMethodsByLine(cls.getMethods());
|
||||
for (MethodNode mth : methods) {
|
||||
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 static List<MethodNode> sortMethodsByLine(List<MethodNode> methods) {
|
||||
List<MethodNode> out = new ArrayList<MethodNode>(methods);
|
||||
Collections.sort(out, METHOD_LINE_COMPARATOR);
|
||||
return out;
|
||||
}
|
||||
|
||||
private boolean isMethodsPresents() {
|
||||
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());
|
||||
@@ -246,6 +297,15 @@ public class ClassGen {
|
||||
code.startLine("/* JADX WARNING: inconsistent code. */");
|
||||
code.startLine("/* Code decompiled incorrectly, please refer to instructions dump. */");
|
||||
ErrorsCounter.methodError(mth, "Inconsistent code");
|
||||
if (showInconsistentCode) {
|
||||
mth.remove(AFlag.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 +344,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) {
|
||||
@@ -293,7 +362,7 @@ public class ClassGen {
|
||||
for (Iterator<EnumField> it = enumFields.getFields().iterator(); it.hasNext(); ) {
|
||||
EnumField f = it.next();
|
||||
code.startLine(f.getName());
|
||||
if (f.getArgs().size() != 0) {
|
||||
if (!f.getArgs().isEmpty()) {
|
||||
code.add('(');
|
||||
for (Iterator<InsnArg> aIt = f.getArgs().iterator(); aIt.hasNext(); ) {
|
||||
InsnArg arg = aIt.next();
|
||||
@@ -310,21 +379,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) {
|
||||
@@ -347,8 +418,8 @@ public class ClassGen {
|
||||
code.attachAnnotation(classNode);
|
||||
}
|
||||
String baseClass = useClassInternal(cls.getClassInfo(), classInfo);
|
||||
ArgType[] generics = classInfo.getType().getGenericTypes();
|
||||
code.add(baseClass);
|
||||
ArgType[] generics = classInfo.getType().getGenericTypes();
|
||||
if (generics != null) {
|
||||
code.add('<');
|
||||
int len = generics.length;
|
||||
@@ -395,7 +466,7 @@ public class ClassGen {
|
||||
if (classNode != null && !classNode.getAccessFlags().isPublic()) {
|
||||
return shortName;
|
||||
}
|
||||
if (searchCollision(cls.dex(), useCls, shortName)) {
|
||||
if (searchCollision(cls.dex(), useCls, classInfo)) {
|
||||
return fullName;
|
||||
}
|
||||
if (classInfo.getPackage().equals(useCls.getPackage())) {
|
||||
@@ -441,22 +512,24 @@ public class ClassGen {
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean searchCollision(DexNode dex, ClassInfo useCls, String shortName) {
|
||||
private static boolean searchCollision(DexNode dex, ClassInfo useCls, ClassInfo searchCls) {
|
||||
if (useCls == null) {
|
||||
return false;
|
||||
}
|
||||
String shortName = searchCls.getShortName();
|
||||
if (useCls.getShortName().equals(shortName)) {
|
||||
return true;
|
||||
}
|
||||
ClassNode classNode = dex.resolveClass(useCls);
|
||||
if (classNode != null) {
|
||||
for (ClassNode inner : classNode.getInnerClasses()) {
|
||||
if (inner.getShortName().equals(shortName)) {
|
||||
if (inner.getShortName().equals(shortName)
|
||||
&& !inner.getClassInfo().equals(searchCls)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return searchCollision(dex, useCls.getParentClass(), shortName);
|
||||
return searchCollision(dex, useCls.getParentClass(), searchCls);
|
||||
}
|
||||
|
||||
private void insertSourceFileInfo(CodeWriter code, AttrNode node) {
|
||||
|
||||
@@ -15,15 +15,11 @@ public class CodeGen extends AbstractVisitor {
|
||||
|
||||
@Override
|
||||
public boolean visit(ClassNode cls) throws CodegenException {
|
||||
ClassGen clsGen = new ClassGen(cls, null, isFallbackMode());
|
||||
ClassGen clsGen = new ClassGen(cls, null, args);
|
||||
CodeWriter clsCode = clsGen.makeClass();
|
||||
clsCode.finish();
|
||||
cls.setCode(clsCode);
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isFallbackMode() {
|
||||
return args.isFallbackMode();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package jadx.core.codegen;
|
||||
|
||||
import jadx.api.CodePosition;
|
||||
import jadx.core.dex.attributes.nodes.LineAttrNode;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.files.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.PrintWriter;
|
||||
@@ -22,6 +22,8 @@ public class CodeWriter {
|
||||
public static final String NL = System.getProperty("line.separator");
|
||||
public static final String INDENT = " ";
|
||||
|
||||
private static final boolean ADD_LINE_NUMBERS = false;
|
||||
|
||||
private static final String[] INDENT_CACHE = {
|
||||
"",
|
||||
INDENT,
|
||||
@@ -43,6 +45,9 @@ public class CodeWriter {
|
||||
public CodeWriter() {
|
||||
this.indent = 0;
|
||||
this.indentStr = "";
|
||||
if (ADD_LINE_NUMBERS) {
|
||||
incIndent(2);
|
||||
}
|
||||
}
|
||||
|
||||
public CodeWriter startLine() {
|
||||
@@ -65,6 +70,26 @@ public class CodeWriter {
|
||||
return this;
|
||||
}
|
||||
|
||||
public CodeWriter startLineWithNum(int sourceLine) {
|
||||
if (sourceLine == 0) {
|
||||
startLine();
|
||||
return this;
|
||||
}
|
||||
if (ADD_LINE_NUMBERS) {
|
||||
newLine();
|
||||
attachSourceLine(sourceLine);
|
||||
String ln = "/* " + sourceLine + " */ ";
|
||||
add(ln);
|
||||
if (indentStr.length() > ln.length()) {
|
||||
add(indentStr.substring(ln.length()));
|
||||
}
|
||||
} else {
|
||||
startLine();
|
||||
attachSourceLine(sourceLine);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public CodeWriter add(String str) {
|
||||
buf.append(str);
|
||||
offset += str.length();
|
||||
@@ -263,11 +288,11 @@ public class CodeWriter {
|
||||
|
||||
PrintWriter out = null;
|
||||
try {
|
||||
Utils.makeDirsForFile(file);
|
||||
FileUtils.makeDirsForFile(file);
|
||||
out = new PrintWriter(file, "UTF-8");
|
||||
String code = buf.toString();
|
||||
code = removeFirstEmptyLine(code);
|
||||
out.print(code);
|
||||
out.println(code);
|
||||
} catch (Exception e) {
|
||||
LOG.error("Save file error", e);
|
||||
} finally {
|
||||
|
||||
@@ -8,13 +8,16 @@ import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.InsnWrapArg;
|
||||
import jadx.core.dex.instructions.args.LiteralArg;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.regions.Compare;
|
||||
import jadx.core.dex.regions.IfCondition;
|
||||
import jadx.core.dex.regions.conditions.Compare;
|
||||
import jadx.core.dex.regions.conditions.IfCondition;
|
||||
import jadx.core.dex.regions.conditions.IfCondition.Mode;
|
||||
import jadx.core.utils.ErrorsCounter;
|
||||
import jadx.core.utils.exceptions.CodegenException;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Queue;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -22,42 +25,83 @@ import org.slf4j.LoggerFactory;
|
||||
public class ConditionGen extends InsnGen {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ConditionGen.class);
|
||||
|
||||
private static class CondStack {
|
||||
private final Queue<IfCondition> stack = new LinkedList<IfCondition>();
|
||||
|
||||
public Queue<IfCondition> getStack() {
|
||||
return stack;
|
||||
}
|
||||
|
||||
public void push(IfCondition cond) {
|
||||
stack.add(cond);
|
||||
}
|
||||
|
||||
public IfCondition pop() {
|
||||
return stack.poll();
|
||||
}
|
||||
}
|
||||
|
||||
public ConditionGen(InsnGen insnGen) {
|
||||
super(insnGen.mgen, insnGen.fallback);
|
||||
}
|
||||
|
||||
void add(CodeWriter code, IfCondition condition) throws CodegenException {
|
||||
add(code, new CondStack(), condition);
|
||||
}
|
||||
|
||||
void wrap(CodeWriter code, IfCondition condition) throws CodegenException {
|
||||
wrap(code, new CondStack(), condition);
|
||||
}
|
||||
|
||||
private void add(CodeWriter code, CondStack stack, IfCondition condition) throws CodegenException {
|
||||
stack.push(condition);
|
||||
switch (condition.getMode()) {
|
||||
case COMPARE:
|
||||
addCompare(code, condition.getCompare());
|
||||
addCompare(code, stack, condition.getCompare());
|
||||
break;
|
||||
|
||||
case TERNARY:
|
||||
addTernary(code, stack, condition);
|
||||
break;
|
||||
|
||||
case NOT:
|
||||
addNot(code, condition);
|
||||
addNot(code, stack, condition);
|
||||
break;
|
||||
|
||||
case AND:
|
||||
case OR:
|
||||
addAndOr(code, condition);
|
||||
addAndOr(code, stack, condition);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new JadxRuntimeException("Unknown condition mode: " + condition);
|
||||
throw new JadxRuntimeException("Unknown condition mode: " + condition.getMode());
|
||||
}
|
||||
stack.pop();
|
||||
}
|
||||
|
||||
void wrap(CodeWriter code, IfCondition cond) throws CodegenException {
|
||||
private void wrap(CodeWriter code, CondStack stack, IfCondition cond) throws CodegenException {
|
||||
boolean wrap = isWrapNeeded(cond);
|
||||
if (wrap) {
|
||||
code.add('(');
|
||||
}
|
||||
add(code, cond);
|
||||
add(code, stack, cond);
|
||||
if (wrap) {
|
||||
code.add(')');
|
||||
}
|
||||
}
|
||||
|
||||
private void addCompare(CodeWriter code, Compare compare) throws CodegenException {
|
||||
private void wrap(CodeWriter code, InsnArg firstArg) throws CodegenException {
|
||||
boolean wrap = isArgWrapNeeded(firstArg);
|
||||
if (wrap) {
|
||||
code.add('(');
|
||||
}
|
||||
addArg(code, firstArg, false);
|
||||
if (wrap) {
|
||||
code.add(')');
|
||||
}
|
||||
}
|
||||
|
||||
private void addCompare(CodeWriter code, CondStack stack, Compare compare) throws CodegenException {
|
||||
IfOp op = compare.getOp();
|
||||
InsnArg firstArg = compare.getA();
|
||||
InsnArg secondArg = compare.getB();
|
||||
@@ -70,39 +114,44 @@ public class ConditionGen extends InsnGen {
|
||||
}
|
||||
if (op == IfOp.EQ) {
|
||||
// == true
|
||||
addArg(code, firstArg, false);
|
||||
if (stack.getStack().size() == 1) {
|
||||
addArg(code, firstArg, false);
|
||||
} else {
|
||||
wrap(code, firstArg);
|
||||
}
|
||||
return;
|
||||
} else if (op == IfOp.NE) {
|
||||
// != true
|
||||
code.add('!');
|
||||
boolean wrap = isWrapNeeded(firstArg);
|
||||
if (wrap) {
|
||||
code.add('(');
|
||||
}
|
||||
addArg(code, firstArg, false);
|
||||
if (wrap) {
|
||||
code.add(')');
|
||||
}
|
||||
wrap(code, firstArg);
|
||||
return;
|
||||
}
|
||||
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 {
|
||||
private void addTernary(CodeWriter code, CondStack stack, IfCondition condition) throws CodegenException {
|
||||
add(code, stack, condition.first());
|
||||
code.add(" ? ");
|
||||
add(code, stack, condition.second());
|
||||
code.add(" : ");
|
||||
add(code, stack, condition.third());
|
||||
}
|
||||
|
||||
private void addNot(CodeWriter code, CondStack stack, IfCondition condition) throws CodegenException {
|
||||
code.add('!');
|
||||
wrap(code, condition.getArgs().get(0));
|
||||
wrap(code, stack, condition.getArgs().get(0));
|
||||
}
|
||||
|
||||
private void addAndOr(CodeWriter code, IfCondition condition) throws CodegenException {
|
||||
String mode = condition.getMode() == IfCondition.Mode.AND ? " && " : " || ";
|
||||
private void addAndOr(CodeWriter code, CondStack stack, IfCondition condition) throws CodegenException {
|
||||
String mode = condition.getMode() == Mode.AND ? " && " : " || ";
|
||||
Iterator<IfCondition> it = condition.getArgs().iterator();
|
||||
while (it.hasNext()) {
|
||||
wrap(code, it.next());
|
||||
wrap(code, stack, it.next());
|
||||
if (it.hasNext()) {
|
||||
code.add(mode);
|
||||
}
|
||||
@@ -110,15 +159,22 @@ public class ConditionGen extends InsnGen {
|
||||
}
|
||||
|
||||
private boolean isWrapNeeded(IfCondition condition) {
|
||||
return !condition.isCompare();
|
||||
if (condition.isCompare()) {
|
||||
return false;
|
||||
}
|
||||
if (condition.getMode() != Mode.NOT) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
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 +183,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;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package jadx.core.codegen;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
|
||||
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
|
||||
import jadx.core.dex.attributes.nodes.MethodInlineAttr;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.info.FieldInfo;
|
||||
@@ -24,7 +25,7 @@ import jadx.core.dex.instructions.args.FieldArg;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.InsnWrapArg;
|
||||
import jadx.core.dex.instructions.args.LiteralArg;
|
||||
import jadx.core.dex.instructions.args.NamedArg;
|
||||
import jadx.core.dex.instructions.args.Named;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.instructions.mods.ConstructorInsn;
|
||||
import jadx.core.dex.instructions.mods.TernaryInsn;
|
||||
@@ -46,7 +47,9 @@ import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -58,9 +61,10 @@ public class InsnGen {
|
||||
protected final RootNode root;
|
||||
protected final boolean fallback;
|
||||
|
||||
private static enum Flags {
|
||||
protected enum Flags {
|
||||
BODY_ONLY,
|
||||
BODY_ONLY_NOWRAP,
|
||||
INLINE
|
||||
}
|
||||
|
||||
public InsnGen(MethodGen mgen, boolean fallback) {
|
||||
@@ -95,7 +99,7 @@ public class InsnGen {
|
||||
Flags flag = wrap ? Flags.BODY_ONLY : Flags.BODY_ONLY_NOWRAP;
|
||||
makeInsn(((InsnWrapArg) arg).getWrapInsn(), code, flag);
|
||||
} else if (arg.isNamed()) {
|
||||
code.add(((NamedArg) arg).getName());
|
||||
code.add(((Named) arg).getName());
|
||||
} else if (arg.isField()) {
|
||||
FieldArg f = (FieldArg) arg;
|
||||
if (f.isStatic()) {
|
||||
@@ -128,7 +132,14 @@ public class InsnGen {
|
||||
}
|
||||
|
||||
private void instanceField(CodeWriter code, FieldInfo field, InsnArg arg) throws CodegenException {
|
||||
FieldNode fieldNode = mth.getParentClass().searchField(field);
|
||||
ClassNode pCls = mth.getParentClass();
|
||||
FieldNode fieldNode = pCls.searchField(field);
|
||||
while (fieldNode == null
|
||||
&& pCls.getParentClass() != pCls
|
||||
&& pCls.getParentClass() != null) {
|
||||
pCls = pCls.getParentClass();
|
||||
fieldNode = pCls.searchField(field);
|
||||
}
|
||||
if (fieldNode != null) {
|
||||
FieldReplaceAttr replace = fieldNode.get(AType.FIELD_REPLACE);
|
||||
if (replace != null) {
|
||||
@@ -178,7 +189,7 @@ public class InsnGen {
|
||||
mgen.getClassGen().useClass(code, cls);
|
||||
}
|
||||
|
||||
private void useType(CodeWriter code, ArgType type) {
|
||||
protected void useType(CodeWriter code, ArgType type) {
|
||||
mgen.getClassGen().useType(code, type);
|
||||
}
|
||||
|
||||
@@ -186,26 +197,27 @@ public class InsnGen {
|
||||
return makeInsn(insn, code, null);
|
||||
}
|
||||
|
||||
private boolean makeInsn(InsnNode insn, CodeWriter code, Flags flag) throws CodegenException {
|
||||
protected boolean makeInsn(InsnNode insn, CodeWriter code, Flags flag) throws CodegenException {
|
||||
try {
|
||||
if (insn.getType() == InsnType.NOP) {
|
||||
return false;
|
||||
}
|
||||
EnumSet<Flags> state = EnumSet.noneOf(Flags.class);
|
||||
Set<Flags> state = EnumSet.noneOf(Flags.class);
|
||||
if (flag == Flags.BODY_ONLY || flag == Flags.BODY_ONLY_NOWRAP) {
|
||||
state.add(flag);
|
||||
makeInsnBody(code, insn, state);
|
||||
} else {
|
||||
code.startLine();
|
||||
if (insn.getSourceLine() != 0) {
|
||||
code.attachSourceLine(insn.getSourceLine());
|
||||
if (flag != Flags.INLINE) {
|
||||
code.startLineWithNum(insn.getSourceLine());
|
||||
}
|
||||
if (insn.getResult() != null && insn.getType() != InsnType.ARITH_ONEARG) {
|
||||
if (insn.getResult() != null && !insn.contains(AFlag.ARITH_ONEARG)) {
|
||||
assignVar(code, insn);
|
||||
code.add(" = ");
|
||||
}
|
||||
makeInsnBody(code, insn, state);
|
||||
code.add(';');
|
||||
if (flag != Flags.INLINE) {
|
||||
code.add(';');
|
||||
}
|
||||
}
|
||||
} catch (Throwable th) {
|
||||
throw new CodegenException(mth, "Error generate insn: " + insn, th);
|
||||
@@ -213,7 +225,7 @@ public class InsnGen {
|
||||
return true;
|
||||
}
|
||||
|
||||
private void makeInsnBody(CodeWriter code, InsnNode insn, EnumSet<Flags> state) throws CodegenException {
|
||||
private void makeInsnBody(CodeWriter code, InsnNode insn, Set<Flags> state) throws CodegenException {
|
||||
switch (insn.getType()) {
|
||||
case CONST_STR:
|
||||
String str = ((ConstStringNode) insn).getString();
|
||||
@@ -255,10 +267,6 @@ public class InsnGen {
|
||||
makeArith((ArithNode) insn, code, state);
|
||||
break;
|
||||
|
||||
case ARITH_ONEARG:
|
||||
makeArithOneArg((ArithNode) insn, code);
|
||||
break;
|
||||
|
||||
case NEG: {
|
||||
boolean wrap = state.contains(Flags.BODY_ONLY);
|
||||
if (wrap) {
|
||||
@@ -283,6 +291,10 @@ public class InsnGen {
|
||||
|
||||
case BREAK:
|
||||
code.add("break");
|
||||
LoopLabelAttr labelAttr = insn.get(AType.LOOP_LABEL);
|
||||
if (labelAttr != null) {
|
||||
code.add(' ').add(mgen.getNameGen().getLoopLabel(labelAttr));
|
||||
}
|
||||
break;
|
||||
|
||||
case CONTINUE:
|
||||
@@ -304,7 +316,7 @@ public class InsnGen {
|
||||
addArg(code, insn.getArg(0));
|
||||
code.add(" == ");
|
||||
addArg(code, insn.getArg(1));
|
||||
code.add("? 0 : -1))");
|
||||
code.add(" ? 0 : -1))");
|
||||
break;
|
||||
|
||||
case INSTANCE_OF: {
|
||||
@@ -425,19 +437,11 @@ public class InsnGen {
|
||||
}
|
||||
break;
|
||||
|
||||
case MOVE_EXCEPTION:
|
||||
if (isFallback()) {
|
||||
code.add("move-exception");
|
||||
} else {
|
||||
addArg(code, insn.getArg(0));
|
||||
}
|
||||
break;
|
||||
|
||||
case TERNARY:
|
||||
makeTernary((TernaryInsn) insn, code, state);
|
||||
break;
|
||||
|
||||
case ARGS:
|
||||
case ONE_ARG:
|
||||
addArg(code, insn.getArg(0));
|
||||
break;
|
||||
|
||||
@@ -461,6 +465,11 @@ public class InsnGen {
|
||||
code.add("goto ").add(MethodGen.getLabelName(((GotoNode) insn).getTarget()));
|
||||
break;
|
||||
|
||||
case MOVE_EXCEPTION:
|
||||
assert isFallback();
|
||||
code.add("move-exception");
|
||||
break;
|
||||
|
||||
case SWITCH:
|
||||
assert isFallback();
|
||||
SwitchNode sw = (SwitchNode) insn;
|
||||
@@ -496,7 +505,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(", ");
|
||||
}
|
||||
@@ -505,6 +514,13 @@ public class InsnGen {
|
||||
}
|
||||
|
||||
private void fillArray(FillArrayNode insn, CodeWriter code) throws CodegenException {
|
||||
String filledArray = makeArrayElements(insn);
|
||||
code.add("new ");
|
||||
useType(code, insn.getElementType());
|
||||
code.add("[]{").add(filledArray).add('}');
|
||||
}
|
||||
|
||||
private String makeArrayElements(FillArrayNode insn) throws CodegenException {
|
||||
ArgType insnArrayType = insn.getResult().getType();
|
||||
ArgType insnElementType = insnArrayType.getArrayElement();
|
||||
ArgType elType = insn.getElementType();
|
||||
@@ -513,10 +529,13 @@ public class InsnGen {
|
||||
"Incorrect type for fill-array insn " + InsnUtils.formatOffset(insn.getOffset())
|
||||
+ ", element type: " + elType + ", insn element type: " + insnElementType
|
||||
);
|
||||
if (!elType.isTypeKnown()) {
|
||||
elType = insnElementType.isTypeKnown() ? insnElementType : elType.selectFirst();
|
||||
}
|
||||
}
|
||||
if (!elType.isTypeKnown()) {
|
||||
LOG.warn("Unknown array element type: {} in mth: {}", elType, mth);
|
||||
elType = insnElementType.isTypeKnown() ? insnElementType : elType.selectFirst();
|
||||
}
|
||||
insn.mergeElementType(elType);
|
||||
|
||||
StringBuilder str = new StringBuilder();
|
||||
Object data = insn.getData();
|
||||
switch (elType.getPrimitiveType()) {
|
||||
@@ -558,15 +577,13 @@ public class InsnGen {
|
||||
}
|
||||
int len = str.length();
|
||||
str.delete(len - 2, len);
|
||||
code.add("new ");
|
||||
useType(code, elType);
|
||||
code.add("[]{").add(str.toString()).add('}');
|
||||
return str.toString();
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -604,7 +621,7 @@ public class InsnGen {
|
||||
code.add("new ");
|
||||
useClass(code, insn.getClassType());
|
||||
}
|
||||
generateArguments(code, insn, 0, mth.dex().resolveMethod(insn.getCallMth()));
|
||||
generateMethodArguments(code, insn, 0, mth.dex().resolveMethod(insn.getCallMth()));
|
||||
}
|
||||
|
||||
private void makeInvoke(InvokeNode insn, CodeWriter code) throws CodegenException {
|
||||
@@ -649,47 +666,74 @@ public class InsnGen {
|
||||
code.attachAnnotation(callMthNode);
|
||||
}
|
||||
code.add(callMth.getName());
|
||||
generateArguments(code, insn, k, callMthNode);
|
||||
generateMethodArguments(code, insn, k, callMthNode);
|
||||
}
|
||||
|
||||
private void generateArguments(CodeWriter code, InsnNode insn, int k, MethodNode callMth) throws CodegenException {
|
||||
private void generateMethodArguments(CodeWriter code, InsnNode insn, int startArgNum,
|
||||
@Nullable MethodNode callMth) throws CodegenException {
|
||||
int k = startArgNum;
|
||||
if (callMth != null && callMth.contains(AFlag.SKIP_FIRST_ARG)) {
|
||||
k++;
|
||||
}
|
||||
int argsCount = insn.getArgsCount();
|
||||
if (callMth != null && callMth.isArgsOverload()) {
|
||||
// add additional argument casts for overloaded methods
|
||||
List<ArgType> originalType = callMth.getMethodInfo().getArgumentsTypes();
|
||||
int origPos = 0;
|
||||
code.add('(');
|
||||
code.add('(');
|
||||
if (k < argsCount) {
|
||||
boolean overloaded = callMth != null && callMth.isArgsOverload();
|
||||
for (int i = k; i < argsCount; i++) {
|
||||
InsnArg arg = insn.getArg(i);
|
||||
ArgType origType = originalType.get(origPos);
|
||||
if (!arg.getType().equals(origType)) {
|
||||
code.add('(');
|
||||
useType(code, origType);
|
||||
code.add(')');
|
||||
addArg(code, arg, true);
|
||||
} else {
|
||||
addArg(code, arg, false);
|
||||
boolean cast = overloaded && processOverloadedArg(code, callMth, arg, i - startArgNum);
|
||||
if (!cast && i == argsCount - 1 && processVarArg(code, callMth, arg)) {
|
||||
continue;
|
||||
}
|
||||
addArg(code, arg, false);
|
||||
if (i < argsCount - 1) {
|
||||
code.add(", ");
|
||||
}
|
||||
origPos++;
|
||||
}
|
||||
code.add(')');
|
||||
} else {
|
||||
}
|
||||
code.add(')');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add additional cast for overloaded method argument.
|
||||
*/
|
||||
private boolean processOverloadedArg(CodeWriter code, MethodNode callMth, InsnArg arg, int origPos) {
|
||||
ArgType origType = callMth.getMethodInfo().getArgumentsTypes().get(origPos);
|
||||
if (!arg.getType().equals(origType)) {
|
||||
code.add('(');
|
||||
if (k < argsCount) {
|
||||
addArg(code, insn.getArg(k), false);
|
||||
for (int i = k + 1; i < argsCount; i++) {
|
||||
useType(code, origType);
|
||||
code.add(") ");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand varArgs from filled array.
|
||||
*/
|
||||
private boolean processVarArg(CodeWriter code, MethodNode callMth, InsnArg lastArg) throws CodegenException {
|
||||
if (callMth == null || !callMth.getAccessFlags().isVarArgs()) {
|
||||
return false;
|
||||
}
|
||||
if (!lastArg.getType().isArray() || !lastArg.isInsnWrap()) {
|
||||
return false;
|
||||
}
|
||||
InsnNode insn = ((InsnWrapArg) lastArg).getWrapInsn();
|
||||
if (insn.getType() == InsnType.FILLED_NEW_ARRAY) {
|
||||
int count = insn.getArgsCount();
|
||||
for (int i = 0; i < count; i++) {
|
||||
InsnArg elemArg = insn.getArg(i);
|
||||
addArg(code, elemArg, false);
|
||||
if (i < count - 1) {
|
||||
code.add(", ");
|
||||
addArg(code, insn.getArg(i), false);
|
||||
}
|
||||
}
|
||||
code.add(')');
|
||||
return true;
|
||||
} else if (insn.getType() == InsnType.FILL_ARRAY) {
|
||||
code.add(makeArrayElements((FillArrayNode) insn));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean inlineMethod(MethodNode callMthNode, InvokeNode insn, CodeWriter code) throws CodegenException {
|
||||
@@ -736,7 +780,7 @@ public class InsnGen {
|
||||
return true;
|
||||
}
|
||||
|
||||
private void makeTernary(TernaryInsn insn, CodeWriter code, EnumSet<Flags> state) throws CodegenException {
|
||||
private void makeTernary(TernaryInsn insn, CodeWriter code, Set<Flags> state) throws CodegenException {
|
||||
boolean wrap = state.contains(Flags.BODY_ONLY);
|
||||
if (wrap) {
|
||||
code.add('(');
|
||||
@@ -758,7 +802,11 @@ public class InsnGen {
|
||||
}
|
||||
}
|
||||
|
||||
private void makeArith(ArithNode insn, CodeWriter code, EnumSet<Flags> state) throws CodegenException {
|
||||
private void makeArith(ArithNode insn, CodeWriter code, Set<Flags> state) throws CodegenException {
|
||||
if (insn.contains(AFlag.ARITH_ONEARG)) {
|
||||
makeArithOneArg(insn, code);
|
||||
return;
|
||||
}
|
||||
// wrap insn in brackets for save correct operation order
|
||||
boolean wrap = state.contains(Flags.BODY_ONLY) && !insn.contains(AFlag.DONT_WRAP);
|
||||
if (wrap) {
|
||||
@@ -776,7 +824,7 @@ public class InsnGen {
|
||||
|
||||
private void makeArithOneArg(ArithNode insn, CodeWriter code) throws CodegenException {
|
||||
ArithOp op = insn.getOp();
|
||||
InsnArg arg = insn.getArg(0);
|
||||
InsnArg arg = insn.getArg(1);
|
||||
// "++" or "--"
|
||||
if (arg.isLiteral() && (op == ArithOp.ADD || op == ArithOp.SUB)) {
|
||||
LiteralArg lit = (LiteralArg) arg;
|
||||
|
||||
@@ -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;
|
||||
@@ -79,8 +78,8 @@ public class MethodGen {
|
||||
if (clsAccFlags.isAnnotation()) {
|
||||
ai = ai.remove(AccessFlags.ACC_PUBLIC);
|
||||
}
|
||||
code.startLine(ai.makeString());
|
||||
code.attachSourceLine(mth.getSourceLine());
|
||||
code.startLineWithNum(mth.getSourceLine());
|
||||
code.add(ai.makeString());
|
||||
|
||||
if (classGen.addGenericMap(code, mth.getGenericMap())) {
|
||||
code.add(' ');
|
||||
@@ -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);
|
||||
}
|
||||
@@ -223,13 +206,10 @@ public class MethodGen {
|
||||
if (insn == null) {
|
||||
continue;
|
||||
}
|
||||
if (addLabels) {
|
||||
if (insn.contains(AType.JUMP)
|
||||
|| insn.contains(AType.EXC_HANDLER)) {
|
||||
code.decIndent();
|
||||
code.startLine(getLabelName(insn.getOffset()) + ":");
|
||||
code.incIndent();
|
||||
}
|
||||
if (addLabels && (insn.contains(AType.JUMP) || insn.contains(AType.EXC_HANDLER))) {
|
||||
code.decIndent();
|
||||
code.startLine(getLabelName(insn.getOffset()) + ":");
|
||||
code.incIndent();
|
||||
}
|
||||
try {
|
||||
if (insnGen.makeInsn(insn, code)) {
|
||||
@@ -248,7 +228,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);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package jadx.core.codegen;
|
||||
|
||||
import jadx.core.Consts;
|
||||
import jadx.core.deobf.NameMapper;
|
||||
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.instructions.InvokeNode;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
@@ -53,10 +54,7 @@ public class NameGen {
|
||||
return name;
|
||||
}
|
||||
name = getUniqueVarName(name);
|
||||
SSAVar sVar = arg.getSVar();
|
||||
if (sVar != null) {
|
||||
sVar.setName(name);
|
||||
}
|
||||
arg.setName(name);
|
||||
return name;
|
||||
}
|
||||
|
||||
@@ -71,7 +69,16 @@ public class NameGen {
|
||||
}
|
||||
|
||||
public String useArg(RegisterArg arg) {
|
||||
String name = makeArgName(arg);
|
||||
String name = arg.getName();
|
||||
if (name == null) {
|
||||
return getFallbackName(arg);
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
// TODO: avoid name collision with variables names
|
||||
public String getLoopLabel(LoopLabelAttr attr) {
|
||||
String name = "loop" + attr.getLoop().getId();
|
||||
varNames.add(name);
|
||||
return name;
|
||||
}
|
||||
@@ -88,14 +95,10 @@ public class NameGen {
|
||||
}
|
||||
|
||||
private String makeArgName(RegisterArg arg) {
|
||||
String name = arg.getName();
|
||||
if (fallback) {
|
||||
String base = "r" + arg.getRegNum();
|
||||
if (name != null) {
|
||||
return base + "_" + name;
|
||||
}
|
||||
return base;
|
||||
return getFallbackName(arg);
|
||||
}
|
||||
String name = arg.getName();
|
||||
String varName;
|
||||
if (name != null) {
|
||||
if ("this".equals(name)) {
|
||||
@@ -111,6 +114,15 @@ public class NameGen {
|
||||
return varName;
|
||||
}
|
||||
|
||||
private String getFallbackName(RegisterArg arg) {
|
||||
String name = arg.getName();
|
||||
String base = "r" + arg.getRegNum();
|
||||
if (name != null && !name.equals("this")) {
|
||||
return base + "_" + name;
|
||||
}
|
||||
return base;
|
||||
}
|
||||
|
||||
private static String makeNameForType(ArgType type) {
|
||||
if (type.isPrimitive()) {
|
||||
return makeNameForPrimitive(type);
|
||||
@@ -172,7 +184,7 @@ public class NameGen {
|
||||
}
|
||||
}
|
||||
|
||||
public static String getAliasForObject(String name) {
|
||||
private static String getAliasForObject(String name) {
|
||||
return OBJ_ALIAS.get(name);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,27 +4,35 @@ import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.nodes.DeclareVariablesAttr;
|
||||
import jadx.core.dex.attributes.nodes.ForceReturnAttr;
|
||||
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
|
||||
import jadx.core.dex.info.FieldInfo;
|
||||
import jadx.core.dex.instructions.IndexInsnNode;
|
||||
import jadx.core.dex.instructions.SwitchNode;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.NamedArg;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.IBlock;
|
||||
import jadx.core.dex.nodes.IContainer;
|
||||
import jadx.core.dex.nodes.IRegion;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.regions.IfCondition;
|
||||
import jadx.core.dex.regions.IfRegion;
|
||||
import jadx.core.dex.regions.LoopRegion;
|
||||
import jadx.core.dex.regions.Region;
|
||||
import jadx.core.dex.regions.SwitchRegion;
|
||||
import jadx.core.dex.regions.SynchronizedRegion;
|
||||
import jadx.core.dex.trycatch.CatchAttr;
|
||||
import jadx.core.dex.regions.TryCatchRegion;
|
||||
import jadx.core.dex.regions.conditions.IfCondition;
|
||||
import jadx.core.dex.regions.conditions.IfRegion;
|
||||
import jadx.core.dex.regions.loops.ForEachLoop;
|
||||
import jadx.core.dex.regions.loops.ForLoop;
|
||||
import jadx.core.dex.regions.loops.LoopRegion;
|
||||
import jadx.core.dex.regions.loops.LoopType;
|
||||
import jadx.core.dex.trycatch.ExceptionHandler;
|
||||
import jadx.core.dex.trycatch.TryCatchBlock;
|
||||
import jadx.core.utils.ErrorsCounter;
|
||||
import jadx.core.utils.RegionUtils;
|
||||
import jadx.core.utils.exceptions.CodegenException;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -52,6 +60,8 @@ public class RegionGen extends InsnGen {
|
||||
makeSwitch((SwitchRegion) cont, code);
|
||||
} else if (cont instanceof LoopRegion) {
|
||||
makeLoop((LoopRegion) cont, code);
|
||||
} else if (cont instanceof TryCatchRegion) {
|
||||
makeTryCatch((TryCatchRegion) cont, code);
|
||||
} else if (cont instanceof SynchronizedRegion) {
|
||||
makeSynchronizedRegion((SynchronizedRegion) cont, code);
|
||||
}
|
||||
@@ -73,14 +83,9 @@ public class RegionGen extends InsnGen {
|
||||
}
|
||||
|
||||
private void makeSimpleRegion(CodeWriter code, Region region) throws CodegenException {
|
||||
CatchAttr tc = region.get(AType.CATCH_BLOCK);
|
||||
if (tc != null) {
|
||||
makeTryCatch(region, tc.getTryBlock(), code);
|
||||
} else {
|
||||
declareVars(code, region);
|
||||
for (IContainer c : region.getSubBlocks()) {
|
||||
makeRegion(code, c);
|
||||
}
|
||||
declareVars(code, region);
|
||||
for (IContainer c : region.getSubBlocks()) {
|
||||
makeRegion(code, c);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,7 +97,9 @@ public class RegionGen extends InsnGen {
|
||||
|
||||
private void makeSimpleBlock(IBlock block, CodeWriter code) throws CodegenException {
|
||||
for (InsnNode insn : block.getInstructions()) {
|
||||
makeInsn(insn, code);
|
||||
if (!insn.contains(AFlag.SKIP)) {
|
||||
makeInsn(insn, code);
|
||||
}
|
||||
}
|
||||
ForceReturnAttr retAttr = block.get(AType.FORCE_RETURN);
|
||||
if (retAttr != null) {
|
||||
@@ -101,14 +108,11 @@ public class RegionGen extends InsnGen {
|
||||
}
|
||||
|
||||
private void makeIf(IfRegion region, CodeWriter code, boolean newLine) throws CodegenException {
|
||||
if (region.getTernRegion() != null) {
|
||||
makeSimpleBlock(region.getTernRegion().getBlock(), code);
|
||||
return;
|
||||
}
|
||||
if (newLine) {
|
||||
code.startLine();
|
||||
code.startLineWithNum(region.getSourceLine());
|
||||
} else {
|
||||
code.attachSourceLine(region.getSourceLine());
|
||||
}
|
||||
code.attachSourceLine(region.getSourceLine());
|
||||
code.add("if (");
|
||||
new ConditionGen(this).add(code, region.getCondition());
|
||||
code.add(") {");
|
||||
@@ -151,8 +155,7 @@ public class RegionGen extends InsnGen {
|
||||
if (header != null) {
|
||||
List<InsnNode> headerInsns = header.getInstructions();
|
||||
if (headerInsns.size() > 1) {
|
||||
// write not inlined instructions from header
|
||||
mth.add(AFlag.INCONSISTENT_CODE);
|
||||
ErrorsCounter.methodError(mth, "Found not inlined instructions from loop header");
|
||||
int last = headerInsns.size() - 1;
|
||||
for (int i = 0; i < last; i++) {
|
||||
InsnNode insn = headerInsns.get(i);
|
||||
@@ -160,6 +163,10 @@ public class RegionGen extends InsnGen {
|
||||
}
|
||||
}
|
||||
}
|
||||
LoopLabelAttr labelAttr = region.getInfo().getStart().get(AType.LOOP_LABEL);
|
||||
if (labelAttr != null) {
|
||||
code.startLine(mgen.getNameGen().getLoopLabel(labelAttr)).add(':');
|
||||
}
|
||||
|
||||
IfCondition condition = region.getCondition();
|
||||
if (condition == null) {
|
||||
@@ -169,8 +176,35 @@ public class RegionGen extends InsnGen {
|
||||
code.startLine('}');
|
||||
return code;
|
||||
}
|
||||
|
||||
ConditionGen conditionGen = new ConditionGen(this);
|
||||
LoopType type = region.getType();
|
||||
if (type != null) {
|
||||
if (type instanceof ForLoop) {
|
||||
ForLoop forLoop = (ForLoop) type;
|
||||
code.startLine("for (");
|
||||
makeInsn(forLoop.getInitInsn(), code, Flags.INLINE);
|
||||
code.add("; ");
|
||||
conditionGen.add(code, condition);
|
||||
code.add("; ");
|
||||
makeInsn(forLoop.getIncrInsn(), code, Flags.INLINE);
|
||||
code.add(") {");
|
||||
makeRegionIndent(code, region.getBody());
|
||||
code.startLine('}');
|
||||
return code;
|
||||
}
|
||||
if (type instanceof ForEachLoop) {
|
||||
ForEachLoop forEachLoop = (ForEachLoop) type;
|
||||
code.startLine("for (");
|
||||
declareVar(code, forEachLoop.getVarArg());
|
||||
code.add(" : ");
|
||||
addArg(code, forEachLoop.getIterableArg(), false);
|
||||
code.add(") {");
|
||||
makeRegionIndent(code, region.getBody());
|
||||
code.startLine('}');
|
||||
return code;
|
||||
}
|
||||
throw new JadxRuntimeException("Unknown loop type: " + type.getClass());
|
||||
}
|
||||
if (region.isConditionAtEnd()) {
|
||||
code.startLine("do {");
|
||||
makeRegionIndent(code, region.getBody());
|
||||
@@ -199,7 +233,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();
|
||||
|
||||
@@ -209,7 +243,14 @@ public class RegionGen extends InsnGen {
|
||||
IContainer c = sw.getCases().get(i);
|
||||
for (Object k : keys) {
|
||||
code.startLine("case ");
|
||||
if (k instanceof IndexInsnNode) {
|
||||
if (k instanceof FieldNode) {
|
||||
FieldNode fn = (FieldNode) k;
|
||||
if (fn.getParentClass().isEnum()) {
|
||||
code.add(fn.getName());
|
||||
} else {
|
||||
staticField(code, fn.getFieldInfo());
|
||||
}
|
||||
} else if (k instanceof IndexInsnNode) {
|
||||
staticField(code, (FieldInfo) ((IndexInsnNode) k).getIndex());
|
||||
} else {
|
||||
code.add(TypeGen.literalToString((Integer) k, arg.getType()));
|
||||
@@ -240,18 +281,18 @@ public class RegionGen extends InsnGen {
|
||||
}
|
||||
}
|
||||
|
||||
private void makeTryCatch(IContainer region, TryCatchBlock tryCatchBlock, CodeWriter code)
|
||||
throws CodegenException {
|
||||
private void makeTryCatch(TryCatchRegion region, CodeWriter code) throws CodegenException {
|
||||
TryCatchBlock tryCatchBlock = region.geTryCatchBlock();
|
||||
code.startLine("try {");
|
||||
region.remove(AType.CATCH_BLOCK);
|
||||
makeRegionIndent(code, region);
|
||||
makeRegionIndent(code, region.getTryRegion());
|
||||
// TODO: move search of 'allHandler' to 'TryCatchRegion'
|
||||
ExceptionHandler allHandler = null;
|
||||
for (ExceptionHandler handler : tryCatchBlock.getHandlers()) {
|
||||
if (!handler.isCatchAll()) {
|
||||
makeCatchBlock(code, handler);
|
||||
} else {
|
||||
if (allHandler != null) {
|
||||
LOG.warn("Several 'all' handlers in try/catch block in " + mth);
|
||||
LOG.warn("Several 'all' handlers in try/catch block in {}", mth);
|
||||
}
|
||||
allHandler = handler;
|
||||
}
|
||||
@@ -266,20 +307,25 @@ public class RegionGen extends InsnGen {
|
||||
code.startLine('}');
|
||||
}
|
||||
|
||||
private void makeCatchBlock(CodeWriter code, ExceptionHandler handler)
|
||||
throws CodegenException {
|
||||
private void makeCatchBlock(CodeWriter code, ExceptionHandler handler) throws CodegenException {
|
||||
IContainer region = handler.getHandlerRegion();
|
||||
if (region != null) {
|
||||
code.startLine("} catch (");
|
||||
if (region == null) {
|
||||
return;
|
||||
}
|
||||
code.startLine("} catch (");
|
||||
InsnArg arg = handler.getArg();
|
||||
if (arg instanceof RegisterArg) {
|
||||
declareVar(code, (RegisterArg) arg);
|
||||
} else if (arg instanceof NamedArg) {
|
||||
if (handler.isCatchAll()) {
|
||||
code.add("Throwable");
|
||||
} else {
|
||||
useClass(code, handler.getCatchType());
|
||||
}
|
||||
code.add(' ');
|
||||
code.add(mgen.getNameGen().assignNamedArg(handler.getArg()));
|
||||
code.add(") {");
|
||||
makeRegionIndent(code, region);
|
||||
code.add(mgen.getNameGen().assignNamedArg((NamedArg) arg));
|
||||
}
|
||||
code.add(") {");
|
||||
makeRegionIndent(code, region);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,10 +6,17 @@ import jadx.core.utils.StringUtils;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class TypeGen {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(TypeGen.class);
|
||||
|
||||
private 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());
|
||||
}
|
||||
@@ -56,7 +63,8 @@ public class TypeGen {
|
||||
case OBJECT:
|
||||
case ARRAY:
|
||||
if (lit != 0) {
|
||||
throw new JadxRuntimeException("Wrong object literal: " + type + " = " + lit);
|
||||
LOG.warn("Wrong object literal: " + lit + " for type: " + type);
|
||||
return Long.toString(lit);
|
||||
}
|
||||
return "null";
|
||||
|
||||
@@ -66,37 +74,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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ public enum AFlag {
|
||||
DONT_WRAP,
|
||||
|
||||
DONT_SHRINK,
|
||||
DONT_INLINE,
|
||||
DONT_GENERATE,
|
||||
SKIP,
|
||||
|
||||
@@ -23,5 +24,8 @@ public enum AFlag {
|
||||
|
||||
ELSE_IF_CHAIN,
|
||||
|
||||
WRAPPED,
|
||||
ARITH_ONEARG,
|
||||
|
||||
INCONSISTENT_CODE, // warning about incorrect decompilation
|
||||
}
|
||||
|
||||
@@ -4,11 +4,13 @@ import jadx.core.dex.attributes.annotations.AnnotationsList;
|
||||
import jadx.core.dex.attributes.annotations.MethodParameters;
|
||||
import jadx.core.dex.attributes.nodes.DeclareVariablesAttr;
|
||||
import jadx.core.dex.attributes.nodes.EnumClassAttr;
|
||||
import jadx.core.dex.attributes.nodes.EnumMapAttr;
|
||||
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
|
||||
import jadx.core.dex.attributes.nodes.ForceReturnAttr;
|
||||
import jadx.core.dex.attributes.nodes.JadxErrorAttr;
|
||||
import jadx.core.dex.attributes.nodes.JumpInfo;
|
||||
import jadx.core.dex.attributes.nodes.LoopInfo;
|
||||
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
|
||||
import jadx.core.dex.attributes.nodes.MethodInlineAttr;
|
||||
import jadx.core.dex.attributes.nodes.PhiListAttr;
|
||||
import jadx.core.dex.attributes.nodes.SourceFileAttr;
|
||||
@@ -25,6 +27,11 @@ import jadx.core.dex.trycatch.SplitterBlockAttr;
|
||||
*/
|
||||
public class AType<T extends IAttribute> {
|
||||
|
||||
private AType() {
|
||||
}
|
||||
|
||||
public static final int FIELDS_COUNT = 18;
|
||||
|
||||
public static final AType<AttrList<JumpInfo>> JUMP = new AType<AttrList<JumpInfo>>();
|
||||
public static final AType<AttrList<LoopInfo>> LOOP = new AType<AttrList<LoopInfo>>();
|
||||
|
||||
@@ -37,9 +44,11 @@ public class AType<T extends IAttribute> {
|
||||
public static final AType<JadxErrorAttr> JADX_ERROR = new AType<JadxErrorAttr>();
|
||||
public static final AType<MethodInlineAttr> METHOD_INLINE = new AType<MethodInlineAttr>();
|
||||
public static final AType<EnumClassAttr> ENUM_CLASS = new AType<EnumClassAttr>();
|
||||
public static final AType<EnumMapAttr> ENUM_MAP = new AType<EnumMapAttr>();
|
||||
public static final AType<AnnotationsList> ANNOTATION_LIST = new AType<AnnotationsList>();
|
||||
public static final AType<MethodParameters> ANNOTATION_MTH_PARAMETERS = new AType<MethodParameters>();
|
||||
public static final AType<PhiListAttr> PHI_LIST = new AType<PhiListAttr>();
|
||||
public static final AType<SourceFileAttr> SOURCE_FILE = new AType<SourceFileAttr>();
|
||||
public static final AType<DeclareVariablesAttr> DECLARE_VARIABLES = new AType<DeclareVariablesAttr>();
|
||||
public static final AType<LoopLabelAttr> LOOP_LABEL = new AType<LoopLabelAttr>();
|
||||
}
|
||||
|
||||
@@ -12,25 +12,25 @@ public abstract class AttrNode implements IAttributeNode {
|
||||
|
||||
@Override
|
||||
public void add(AFlag flag) {
|
||||
getStorage().add(flag);
|
||||
initStorage().add(flag);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addAttr(IAttribute attr) {
|
||||
getStorage().add(attr);
|
||||
initStorage().add(attr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void addAttr(AType<AttrList<T>> type, T obj) {
|
||||
getStorage().add(type, obj);
|
||||
initStorage().add(type, obj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void copyAttributesFrom(AttrNode attrNode) {
|
||||
getStorage().addAll(attrNode.storage);
|
||||
initStorage().addAll(attrNode.storage);
|
||||
}
|
||||
|
||||
AttributeStorage getStorage() {
|
||||
AttributeStorage initStorage() {
|
||||
AttributeStorage store = storage;
|
||||
if (store == EMPTY_ATTR_STORAGE) {
|
||||
store = new AttributeStorage();
|
||||
|
||||
@@ -7,7 +7,7 @@ import jadx.core.utils.Utils;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
@@ -24,7 +24,7 @@ public class AttributeStorage {
|
||||
|
||||
public AttributeStorage() {
|
||||
flags = EnumSet.noneOf(AFlag.class);
|
||||
attributes = new HashMap<AType<?>, IAttribute>(2);
|
||||
attributes = new IdentityHashMap<AType<?>, IAttribute>(AType.FIELDS_COUNT);
|
||||
}
|
||||
|
||||
public void add(AFlag flag) {
|
||||
@@ -72,7 +72,7 @@ public class AttributeStorage {
|
||||
if (attrList == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return attrList.getList();
|
||||
return Collections.unmodifiableList(attrList.getList());
|
||||
}
|
||||
|
||||
public void remove(AFlag flag) {
|
||||
|
||||
@@ -5,7 +5,7 @@ import jadx.core.dex.attributes.annotations.Annotation;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class EmptyAttrStorage extends AttributeStorage {
|
||||
public final class EmptyAttrStorage extends AttributeStorage {
|
||||
|
||||
@Override
|
||||
public boolean contains(AFlag flag) {
|
||||
@@ -52,4 +52,9 @@ public class EmptyAttrStorage extends AttributeStorage {
|
||||
public List<String> getAttributeStrings() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import java.util.Map;
|
||||
|
||||
public class Annotation {
|
||||
|
||||
public static enum Visibility {
|
||||
public enum Visibility {
|
||||
BUILD, RUNTIME, SYSTEM
|
||||
}
|
||||
|
||||
|
||||
@@ -32,6 +32,10 @@ public class AnnotationsList implements IAttribute {
|
||||
return map.size();
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return map.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AType<AnnotationsList> getType() {
|
||||
return AType.ANNOTATION_LIST;
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
package jadx.core.dex.attributes.nodes;
|
||||
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.IAttribute;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class EnumMapAttr implements IAttribute {
|
||||
|
||||
public static class KeyValueMap {
|
||||
private Map<Object, Object> map = new HashMap<Object, Object>();
|
||||
|
||||
public Object get(Object key) {
|
||||
return map.get(key);
|
||||
}
|
||||
|
||||
void put(Object key, Object value) {
|
||||
map.put(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
private Map<FieldNode, KeyValueMap> fieldsMap = new HashMap<FieldNode, KeyValueMap>();
|
||||
|
||||
public KeyValueMap getMap(FieldNode field) {
|
||||
return fieldsMap.get(field);
|
||||
}
|
||||
|
||||
public void add(FieldNode field, Object key, Object value) {
|
||||
KeyValueMap map = getMap(field);
|
||||
if (map == null) {
|
||||
map = new KeyValueMap();
|
||||
fieldsMap.put(field, map);
|
||||
}
|
||||
map.put(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AType<EnumMapAttr> getType() {
|
||||
return AType.ENUM_MAP;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Enum fields map: " + fieldsMap;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -17,6 +17,9 @@ public class LoopInfo {
|
||||
private final BlockNode end;
|
||||
private final Set<BlockNode> loopBlocks;
|
||||
|
||||
private int id;
|
||||
private LoopInfo parentLoop;
|
||||
|
||||
public LoopInfo(BlockNode start, BlockNode end) {
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
@@ -69,8 +72,24 @@ public class LoopInfo {
|
||||
return edges;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public LoopInfo getParentLoop() {
|
||||
return parentLoop;
|
||||
}
|
||||
|
||||
public void setParentLoop(LoopInfo parentLoop) {
|
||||
this.parentLoop = parentLoop;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "LOOP: " + start + "->" + end;
|
||||
return "LOOP:" + id + ": " + start + "->" + end;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package jadx.core.dex.attributes.nodes;
|
||||
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.IAttribute;
|
||||
|
||||
public class LoopLabelAttr implements IAttribute {
|
||||
|
||||
private final LoopInfo loop;
|
||||
|
||||
public LoopLabelAttr(LoopInfo loop) {
|
||||
this.loop = loop;
|
||||
}
|
||||
|
||||
public LoopInfo getLoop() {
|
||||
return loop;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AType<LoopLabelAttr> getType() {
|
||||
return AType.LOOP_LABEL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "LOOP_LABEL: " + loop;
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ public class AccessInfo {
|
||||
|
||||
private final int accFlags;
|
||||
|
||||
public static enum AFType {
|
||||
public enum AFType {
|
||||
CLASS, FIELD, METHOD
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package jadx.core.dex.instructions;
|
||||
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
@@ -51,10 +52,8 @@ public class ArithNode extends InsnNode {
|
||||
}
|
||||
|
||||
public ArithNode(ArithOp op, RegisterArg res, InsnArg a) {
|
||||
super(InsnType.ARITH_ONEARG, 1);
|
||||
this.op = op;
|
||||
setResult(res);
|
||||
addArg(a);
|
||||
this(op, res, res, a);
|
||||
add(AFlag.ARITH_ONEARG);
|
||||
}
|
||||
|
||||
public ArithOp getOp() {
|
||||
@@ -85,7 +84,7 @@ public class ArithNode extends InsnNode {
|
||||
+ getResult() + " = "
|
||||
+ getArg(0) + " "
|
||||
+ op.getSymbol() + " "
|
||||
+ (getArgsCount() == 2 ? getArg(1) : "");
|
||||
+ getArg(1);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -4,16 +4,14 @@ import jadx.core.dex.info.FieldInfo;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.NamedArg;
|
||||
import jadx.core.dex.instructions.args.PrimitiveType;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.nodes.DexNode;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.utils.InsnUtils;
|
||||
import jadx.core.utils.exceptions.DecodeException;
|
||||
|
||||
import java.io.EOFException;
|
||||
|
||||
import com.android.dex.Code;
|
||||
import com.android.dx.io.OpcodeInfo;
|
||||
import com.android.dx.io.Opcodes;
|
||||
@@ -44,7 +42,7 @@ public class InsnDecoder {
|
||||
while (in.hasMore()) {
|
||||
decoded[in.cursor()] = DecodedInstruction.decode(in);
|
||||
}
|
||||
} catch (EOFException e) {
|
||||
} catch (Exception e) {
|
||||
throw new DecodeException(method, "", e);
|
||||
}
|
||||
insnArr = decoded;
|
||||
@@ -406,8 +404,7 @@ public class InsnDecoder {
|
||||
|
||||
case Opcodes.MOVE_EXCEPTION:
|
||||
return insn(InsnType.MOVE_EXCEPTION,
|
||||
InsnArg.reg(insn, 0, ArgType.unknown(PrimitiveType.OBJECT)),
|
||||
new NamedArg("e", ArgType.unknown(PrimitiveType.OBJECT)));
|
||||
InsnArg.reg(insn, 0, ArgType.unknown(PrimitiveType.OBJECT)));
|
||||
|
||||
case Opcodes.RETURN_VOID:
|
||||
return new InsnNode(InsnType.RETURN, 0);
|
||||
@@ -624,16 +621,19 @@ public class InsnDecoder {
|
||||
int resReg = getMoveResultRegister(insnArr, offset);
|
||||
ArgType arrType = dex.getType(insn.getIndex());
|
||||
ArgType elType = arrType.getArrayElement();
|
||||
InsnArg[] regs = new InsnArg[insn.getRegisterCount()];
|
||||
boolean typeImmutable = elType.isPrimitive();
|
||||
int regsCount = insn.getRegisterCount();
|
||||
InsnArg[] regs = new InsnArg[regsCount];
|
||||
if (isRange) {
|
||||
int r = insn.getA();
|
||||
for (int i = 0; i < insn.getRegisterCount(); i++) {
|
||||
regs[i] = InsnArg.reg(r, elType);
|
||||
for (int i = 0; i < regsCount; i++) {
|
||||
regs[i] = InsnArg.reg(r, elType, typeImmutable);
|
||||
r++;
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < insn.getRegisterCount(); i++) {
|
||||
regs[i] = InsnArg.reg(insn, i, elType);
|
||||
for (int i = 0; i < regsCount; i++) {
|
||||
int regNum = InsnUtils.getArg(insn, i);
|
||||
regs[i] = InsnArg.reg(regNum, elType, typeImmutable);
|
||||
}
|
||||
}
|
||||
return insn(InsnType.FILLED_NEW_ARRAY,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package jadx.core.dex.instructions;
|
||||
|
||||
public enum InsnType {
|
||||
NOP, // replacement for removed instructions
|
||||
|
||||
CONST,
|
||||
CONST_STR,
|
||||
@@ -48,17 +47,24 @@ public enum InsnType {
|
||||
|
||||
INVOKE,
|
||||
|
||||
// additional instructions
|
||||
// *** Additional instructions ***
|
||||
|
||||
// replacement for removed instructions
|
||||
NOP,
|
||||
|
||||
TERNARY,
|
||||
CONSTRUCTOR,
|
||||
|
||||
BREAK,
|
||||
CONTINUE,
|
||||
|
||||
STR_CONCAT, // strings concatenation
|
||||
ARITH_ONEARG,
|
||||
// strings concatenation
|
||||
STR_CONCAT,
|
||||
|
||||
TERNARY,
|
||||
ARGS, // just generate arguments
|
||||
// just generate one argument
|
||||
ONE_ARG,
|
||||
PHI,
|
||||
|
||||
NEW_MULTIDIM_ARRAY // TODO: now multidimensional arrays created using Array.newInstance function
|
||||
// TODO: now multidimensional arrays created using Array.newInstance function
|
||||
NEW_MULTIDIM_ARRAY
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package jadx.core.dex.instructions;
|
||||
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
@@ -14,6 +15,7 @@ public class PhiInsn extends InsnNode {
|
||||
for (int i = 0; i < predecessors; i++) {
|
||||
addReg(regNum, ArgType.UNKNOWN);
|
||||
}
|
||||
add(AFlag.DONT_INLINE);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -21,6 +23,14 @@ public class PhiInsn extends InsnNode {
|
||||
return (RegisterArg) super.getArg(n);
|
||||
}
|
||||
|
||||
public boolean removeArg(RegisterArg arg) {
|
||||
boolean isRemoved = super.removeArg(arg);
|
||||
if (isRemoved) {
|
||||
arg.getSVar().setUsedInPhi(null);
|
||||
}
|
||||
return isRemoved;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "PHI: " + getResult() + " = " + Utils.listToString(getArguments());
|
||||
|
||||
@@ -9,6 +9,8 @@ import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public abstract class ArgType {
|
||||
|
||||
public static final ArgType INT = primitive(PrimitiveType.INT);
|
||||
@@ -48,6 +50,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);
|
||||
}
|
||||
@@ -89,10 +95,28 @@ public abstract class ArgType {
|
||||
}
|
||||
|
||||
private abstract static class KnownType extends ArgType {
|
||||
|
||||
private static final PrimitiveType[] EMPTY_POSSIBLES = new PrimitiveType[0];
|
||||
|
||||
@Override
|
||||
public boolean isTypeKnown() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(PrimitiveType type) {
|
||||
return getPrimitiveType() == type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArgType selectFirst() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PrimitiveType[] getPossibleTypes() {
|
||||
return EMPTY_POSSIBLES;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class PrimitiveArg extends KnownType {
|
||||
@@ -174,7 +198,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 +238,7 @@ public abstract class ArgType {
|
||||
if (bounds == 0) {
|
||||
return "?";
|
||||
}
|
||||
return "? " + (bounds == -1 ? "super" : "extends") + " " + type.toString();
|
||||
return "? " + (bounds == -1 ? "super" : "extends") + " " + type;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -265,6 +289,7 @@ public abstract class ArgType {
|
||||
}
|
||||
|
||||
private static final class ArrayArg extends KnownType {
|
||||
public static final PrimitiveType[] ARRAY_POSSIBLES = new PrimitiveType[]{PrimitiveType.ARRAY};
|
||||
private final ArgType arrayElement;
|
||||
|
||||
public ArrayArg(ArgType arrayElement) {
|
||||
@@ -287,6 +312,21 @@ public abstract class ArgType {
|
||||
return PrimitiveType.ARRAY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTypeKnown() {
|
||||
return arrayElement.isTypeKnown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArgType selectFirst() {
|
||||
return array(arrayElement.selectFirst());
|
||||
}
|
||||
|
||||
@Override
|
||||
public PrimitiveType[] getPossibleTypes() {
|
||||
return ARRAY_POSSIBLES;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getArrayDimension() {
|
||||
return 1 + arrayElement.getArrayDimension();
|
||||
@@ -339,8 +379,10 @@ public abstract class ArgType {
|
||||
@Override
|
||||
public ArgType selectFirst() {
|
||||
PrimitiveType f = possibleTypes[0];
|
||||
if (f == PrimitiveType.OBJECT || f == PrimitiveType.ARRAY) {
|
||||
return object(Consts.CLASS_OBJECT);
|
||||
if (contains(PrimitiveType.OBJECT)) {
|
||||
return OBJECT;
|
||||
} else if (contains(PrimitiveType.ARRAY)) {
|
||||
return array(OBJECT);
|
||||
} else {
|
||||
return primitive(f);
|
||||
}
|
||||
@@ -398,7 +440,7 @@ public abstract class ArgType {
|
||||
}
|
||||
|
||||
/**
|
||||
* @see jadx.core.dex.instructions.args.ArgType.WildcardType#getWildcardBounds()
|
||||
* @see WildcardType#getWildcardBounds()
|
||||
*/
|
||||
public int getWildcardBounds() {
|
||||
return 0;
|
||||
@@ -424,18 +466,13 @@ public abstract class ArgType {
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean contains(PrimitiveType type) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
public abstract boolean contains(PrimitiveType type);
|
||||
|
||||
public ArgType selectFirst() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
public abstract ArgType selectFirst();
|
||||
|
||||
public PrimitiveType[] getPossibleTypes() {
|
||||
return null;
|
||||
}
|
||||
public abstract PrimitiveType[] getPossibleTypes();
|
||||
|
||||
@Nullable
|
||||
public static ArgType merge(ArgType a, ArgType b) {
|
||||
if (a == null || b == null) {
|
||||
return null;
|
||||
@@ -454,13 +491,18 @@ public abstract class ArgType {
|
||||
if (a == UNKNOWN) {
|
||||
return b;
|
||||
}
|
||||
|
||||
if (a.isArray()) {
|
||||
return mergeArrays((ArrayArg) a, b);
|
||||
} else if (b.isArray()) {
|
||||
return mergeArrays((ArrayArg) b, a);
|
||||
}
|
||||
if (!a.isTypeKnown()) {
|
||||
if (b.isTypeKnown()) {
|
||||
if (a.contains(b.getPrimitiveType())) {
|
||||
return b;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
} else {
|
||||
// both types unknown
|
||||
List<PrimitiveType> types = new ArrayList<PrimitiveType>();
|
||||
@@ -471,7 +513,8 @@ public abstract class ArgType {
|
||||
}
|
||||
if (types.isEmpty()) {
|
||||
return null;
|
||||
} else if (types.size() == 1) {
|
||||
}
|
||||
if (types.size() == 1) {
|
||||
PrimitiveType nt = types.get(0);
|
||||
if (nt == PrimitiveType.OBJECT || nt == PrimitiveType.ARRAY) {
|
||||
return unknown(nt);
|
||||
@@ -495,31 +538,15 @@ public abstract class ArgType {
|
||||
String bObj = b.getObject();
|
||||
if (aObj.equals(bObj)) {
|
||||
return a.getGenericTypes() != null ? a : b;
|
||||
} else if (aObj.equals(Consts.CLASS_OBJECT)) {
|
||||
}
|
||||
if (aObj.equals(Consts.CLASS_OBJECT)) {
|
||||
return b;
|
||||
} else if (bObj.equals(Consts.CLASS_OBJECT)) {
|
||||
}
|
||||
if (bObj.equals(Consts.CLASS_OBJECT)) {
|
||||
return a;
|
||||
} else {
|
||||
// different objects
|
||||
String obj = clsp.getCommonAncestor(aObj, bObj);
|
||||
return obj == null ? null : object(obj);
|
||||
}
|
||||
}
|
||||
if (a.isArray()) {
|
||||
if (b.isArray()) {
|
||||
ArgType ea = a.getArrayElement();
|
||||
ArgType eb = b.getArrayElement();
|
||||
if (ea.isPrimitive() && eb.isPrimitive()) {
|
||||
return OBJECT;
|
||||
} else {
|
||||
ArgType res = merge(ea, eb);
|
||||
return res == null ? null : ArgType.array(res);
|
||||
}
|
||||
} else if (b.equals(OBJECT)) {
|
||||
return OBJECT;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
String obj = clsp.getCommonAncestor(aObj, bObj);
|
||||
return obj == null ? null : object(obj);
|
||||
}
|
||||
if (a.isPrimitive() && b.isPrimitive() && a.getRegCount() == b.getRegCount()) {
|
||||
return primitive(PrimitiveType.getSmaller(a.getPrimitiveType(), b.getPrimitiveType()));
|
||||
@@ -528,6 +555,25 @@ public abstract class ArgType {
|
||||
return null;
|
||||
}
|
||||
|
||||
private static ArgType mergeArrays(ArrayArg array, ArgType b) {
|
||||
if (b.isArray()) {
|
||||
ArgType ea = array.getArrayElement();
|
||||
ArgType eb = b.getArrayElement();
|
||||
if (ea.isPrimitive() && eb.isPrimitive()) {
|
||||
return OBJECT;
|
||||
}
|
||||
ArgType res = merge(ea, eb);
|
||||
return res == null ? null : array(res);
|
||||
}
|
||||
if (b.contains(PrimitiveType.ARRAY)) {
|
||||
return array;
|
||||
}
|
||||
if (b.equals(OBJECT)) {
|
||||
return OBJECT;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static boolean isCastNeeded(ArgType from, ArgType to) {
|
||||
if (from.equals(to)) {
|
||||
return false;
|
||||
@@ -539,6 +585,16 @@ public abstract class ArgType {
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean isInstanceOf(ArgType type, ArgType of) {
|
||||
if (type.equals(of)) {
|
||||
return true;
|
||||
}
|
||||
if (!type.isObject() || !of.isObject()) {
|
||||
return false;
|
||||
}
|
||||
return clsp.isImplements(type.getObject(), of.getObject());
|
||||
}
|
||||
|
||||
public static ArgType parse(String type) {
|
||||
char f = type.charAt(0);
|
||||
switch (f) {
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
package jadx.core.dex.instructions.args;
|
||||
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.utils.InsnUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -26,8 +30,12 @@ public abstract class InsnArg extends Typed {
|
||||
return reg(InsnUtils.getArg(insn, argNum), type);
|
||||
}
|
||||
|
||||
public static MthParameterArg parameterReg(int regNum, ArgType type) {
|
||||
return new MthParameterArg(regNum, type);
|
||||
public static TypeImmutableArg typeImmutableReg(int regNum, ArgType type) {
|
||||
return new TypeImmutableArg(regNum, type);
|
||||
}
|
||||
|
||||
public static RegisterArg reg(int regNum, ArgType type, boolean typeImmutable) {
|
||||
return typeImmutable ? new TypeImmutableArg(regNum, type) : new RegisterArg(regNum, type);
|
||||
}
|
||||
|
||||
public static LiteralArg lit(long literal, ArgType type) {
|
||||
@@ -76,18 +84,36 @@ public abstract class InsnArg extends Typed {
|
||||
return null;
|
||||
}
|
||||
if (parent == insn) {
|
||||
LOG.debug("Can't wrap instruction info itself: " + insn);
|
||||
LOG.debug("Can't wrap instruction info itself: {}", insn);
|
||||
Thread.dumpStack();
|
||||
return null;
|
||||
}
|
||||
int i = getArgIndex(parent, this);
|
||||
if (i == -1) {
|
||||
return null;
|
||||
}
|
||||
insn.add(AFlag.WRAPPED);
|
||||
InsnArg arg = wrapArg(insn);
|
||||
parent.setArg(i, arg);
|
||||
return arg;
|
||||
}
|
||||
|
||||
public static void updateParentInsn(InsnNode fromInsn, InsnNode toInsn) {
|
||||
List<RegisterArg> args = new ArrayList<RegisterArg>();
|
||||
fromInsn.getRegisterArgs(args);
|
||||
for (RegisterArg reg : args) {
|
||||
reg.setParentInsn(toInsn);
|
||||
}
|
||||
}
|
||||
|
||||
private static int getArgIndex(InsnNode parent, InsnArg arg) {
|
||||
int count = parent.getArgsCount();
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (parent.getArg(i) == this) {
|
||||
InsnArg arg = wrapArg(insn);
|
||||
parent.setArg(i, arg);
|
||||
return arg;
|
||||
if (parent.getArg(i) == arg) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static InsnArg wrapArg(InsnNode insn) {
|
||||
|
||||
@@ -38,11 +38,11 @@ public final class LiteralArg extends InsnArg {
|
||||
|
||||
public boolean isInteger() {
|
||||
PrimitiveType type = this.type.getPrimitiveType();
|
||||
return (type == PrimitiveType.INT
|
||||
return type == PrimitiveType.INT
|
||||
|| type == PrimitiveType.BYTE
|
||||
|| type == PrimitiveType.CHAR
|
||||
|| type == PrimitiveType.SHORT
|
||||
|| type == PrimitiveType.LONG);
|
||||
|| type == PrimitiveType.LONG;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -19,7 +19,8 @@ public class RegisterArg extends InsnArg implements Named {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(RegisterArg.class);
|
||||
|
||||
protected final int regNum;
|
||||
protected SSAVar sVar;
|
||||
// not null after SSATransform pass
|
||||
private SSAVar sVar;
|
||||
|
||||
public RegisterArg(int rn) {
|
||||
this.regNum = rn;
|
||||
@@ -80,8 +81,10 @@ public class RegisterArg extends InsnArg implements Named {
|
||||
setName(name);
|
||||
}
|
||||
|
||||
public void forceType(ArgType type) {
|
||||
this.type = type;
|
||||
public RegisterArg duplicate() {
|
||||
RegisterArg dup = new RegisterArg(getRegNum(), getType());
|
||||
dup.setSVar(sVar);
|
||||
return dup;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -138,11 +141,7 @@ public class RegisterArg extends InsnArg implements Named {
|
||||
if (sVar == null) {
|
||||
return null;
|
||||
}
|
||||
RegisterArg assign = sVar.getAssign();
|
||||
if (assign != null) {
|
||||
return assign.getParentInsn();
|
||||
}
|
||||
return null;
|
||||
return sVar.getAssign().getParentInsn();
|
||||
}
|
||||
|
||||
public InsnNode getPhiAssignInsn() {
|
||||
@@ -150,12 +149,9 @@ public class RegisterArg extends InsnArg implements Named {
|
||||
if (usePhi != null) {
|
||||
return usePhi;
|
||||
}
|
||||
RegisterArg assign = sVar.getAssign();
|
||||
if (assign != null) {
|
||||
InsnNode parent = assign.getParentInsn();
|
||||
if (parent != null && parent.getType() == InsnType.PHI) {
|
||||
return parent;
|
||||
}
|
||||
InsnNode parent = sVar.getAssign().getParentInsn();
|
||||
if (parent != null && parent.getType() == InsnType.PHI) {
|
||||
return parent;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -5,25 +5,74 @@ import jadx.core.dex.instructions.PhiInsn;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class SSAVar {
|
||||
|
||||
private final int regNum;
|
||||
private final int version;
|
||||
private VarName varName;
|
||||
|
||||
private int startUseAddr;
|
||||
private int endUseAddr;
|
||||
|
||||
@NotNull
|
||||
private RegisterArg assign;
|
||||
private final List<RegisterArg> useList = new ArrayList<RegisterArg>(2);
|
||||
@Nullable
|
||||
private PhiInsn usedInPhi;
|
||||
|
||||
private ArgType type;
|
||||
private boolean typeImmutable;
|
||||
|
||||
public SSAVar(int regNum, int v, RegisterArg assign) {
|
||||
public SSAVar(int regNum, int v, @NotNull RegisterArg assign) {
|
||||
this.regNum = regNum;
|
||||
this.version = v;
|
||||
this.assign = assign;
|
||||
|
||||
if (assign != null) {
|
||||
assign.setSVar(this);
|
||||
assign.setSVar(this);
|
||||
startUseAddr = -1;
|
||||
endUseAddr = -1;
|
||||
}
|
||||
|
||||
public int getStartAddr() {
|
||||
if (startUseAddr == -1) {
|
||||
calcUsageAddrRange();
|
||||
}
|
||||
return startUseAddr;
|
||||
}
|
||||
|
||||
public int getEndAddr() {
|
||||
if (endUseAddr == -1) {
|
||||
calcUsageAddrRange();
|
||||
}
|
||||
return endUseAddr;
|
||||
}
|
||||
|
||||
private void calcUsageAddrRange() {
|
||||
int start = Integer.MAX_VALUE;
|
||||
int end = Integer.MIN_VALUE;
|
||||
|
||||
if (assign.getParentInsn() != null) {
|
||||
int insnAddr = assign.getParentInsn().getOffset();
|
||||
if (insnAddr >= 0) {
|
||||
start = Math.min(insnAddr, start);
|
||||
end = Math.max(insnAddr, end);
|
||||
}
|
||||
}
|
||||
for (RegisterArg arg : useList) {
|
||||
if (arg.getParentInsn() != null) {
|
||||
int insnAddr = arg.getParentInsn().getOffset();
|
||||
if (insnAddr >= 0) {
|
||||
start = Math.min(insnAddr, start);
|
||||
end = Math.max(insnAddr, end);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (start != Integer.MAX_VALUE && end != Integer.MIN_VALUE) {
|
||||
startUseAddr = start;
|
||||
endUseAddr = end;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,11 +84,12 @@ public class SSAVar {
|
||||
return version;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public RegisterArg getAssign() {
|
||||
return assign;
|
||||
}
|
||||
|
||||
public void setAssign(RegisterArg assign) {
|
||||
public void setAssign(@NotNull RegisterArg assign) {
|
||||
this.assign = assign;
|
||||
}
|
||||
|
||||
@@ -68,10 +118,11 @@ public class SSAVar {
|
||||
}
|
||||
}
|
||||
|
||||
public void setUsedInPhi(PhiInsn usedInPhi) {
|
||||
public void setUsedInPhi(@Nullable PhiInsn usedInPhi) {
|
||||
this.usedInPhi = usedInPhi;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public PhiInsn getUsedInPhi() {
|
||||
return usedInPhi;
|
||||
}
|
||||
@@ -81,24 +132,34 @@ public class SSAVar {
|
||||
}
|
||||
|
||||
public int getVariableUseCount() {
|
||||
if (!isUsedInPhi()) {
|
||||
if (usedInPhi == null) {
|
||||
return useList.size();
|
||||
}
|
||||
return useList.size() + usedInPhi.getResult().getSVar().getUseCount();
|
||||
}
|
||||
|
||||
public ArgType getType() {
|
||||
return type;
|
||||
public void setType(ArgType type) {
|
||||
ArgType acceptedType;
|
||||
if (typeImmutable) {
|
||||
// don't change type, just update types in useList
|
||||
acceptedType = this.type;
|
||||
} else {
|
||||
acceptedType = type;
|
||||
this.type = acceptedType;
|
||||
}
|
||||
assign.type = acceptedType;
|
||||
for (int i = 0, useListSize = useList.size(); i < useListSize; i++) {
|
||||
useList.get(i).type = acceptedType;
|
||||
}
|
||||
}
|
||||
|
||||
public void setType(ArgType type) {
|
||||
this.type = type;
|
||||
if (assign != null) {
|
||||
assign.type = type;
|
||||
}
|
||||
for (int i = 0, useListSize = useList.size(); i < useListSize; i++) {
|
||||
useList.get(i).type = type;
|
||||
}
|
||||
public void setTypeImmutable(ArgType type) {
|
||||
setType(type);
|
||||
this.typeImmutable = true;
|
||||
}
|
||||
|
||||
public boolean isTypeImmutable() {
|
||||
return typeImmutable;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
|
||||
+7
-5
@@ -1,10 +1,10 @@
|
||||
package jadx.core.dex.instructions.args;
|
||||
|
||||
public class MthParameterArg extends RegisterArg {
|
||||
public class TypeImmutableArg extends RegisterArg {
|
||||
|
||||
private boolean isThis = false;
|
||||
private boolean isThis;
|
||||
|
||||
public MthParameterArg(int rn, ArgType type) {
|
||||
public TypeImmutableArg(int rn, ArgType type) {
|
||||
super(rn, type);
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ public class MthParameterArg extends RegisterArg {
|
||||
|
||||
@Override
|
||||
public void setType(ArgType type) {
|
||||
// not allowed
|
||||
}
|
||||
|
||||
public void markAsThis() {
|
||||
@@ -39,6 +40,7 @@ public class MthParameterArg extends RegisterArg {
|
||||
if (isThis) {
|
||||
sVar.setName("this");
|
||||
}
|
||||
sVar.setTypeImmutable(type);
|
||||
super.setSVar(sVar);
|
||||
}
|
||||
|
||||
@@ -47,13 +49,13 @@ public class MthParameterArg extends RegisterArg {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (!(obj instanceof MthParameterArg)) {
|
||||
if (!(obj instanceof TypeImmutableArg)) {
|
||||
return false;
|
||||
}
|
||||
if (!super.equals(obj)) {
|
||||
return false;
|
||||
}
|
||||
MthParameterArg that = (MthParameterArg) obj;
|
||||
TypeImmutableArg that = (TypeImmutableArg) obj;
|
||||
return isThis == that.isThis;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -52,6 +52,13 @@ public class ConstructorInsn extends InsnNode {
|
||||
setSourceLine(invoke.getSourceLine());
|
||||
}
|
||||
|
||||
public ConstructorInsn(MethodInfo callMth, CallType callType, RegisterArg instanceArg) {
|
||||
super(InsnType.CONSTRUCTOR, callMth.getArgsCount());
|
||||
this.callMth = callMth;
|
||||
this.callType = callType;
|
||||
this.instanceArg = instanceArg;
|
||||
}
|
||||
|
||||
public MethodInfo getCallMth() {
|
||||
return callMth;
|
||||
}
|
||||
@@ -64,6 +71,14 @@ public class ConstructorInsn extends InsnNode {
|
||||
return callMth.getDeclClass();
|
||||
}
|
||||
|
||||
public CallType getCallType() {
|
||||
return callType;
|
||||
}
|
||||
|
||||
public boolean isNewInstance() {
|
||||
return callType == CallType.CONSTRUCTOR;
|
||||
}
|
||||
|
||||
public boolean isSuper() {
|
||||
return callType == CallType.SUPER;
|
||||
}
|
||||
|
||||
@@ -5,13 +5,19 @@ import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.LiteralArg;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.regions.IfCondition;
|
||||
import jadx.core.dex.regions.conditions.IfCondition;
|
||||
import jadx.core.utils.InsnUtils;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
public class TernaryInsn extends InsnNode {
|
||||
import java.util.List;
|
||||
|
||||
private final IfCondition condition;
|
||||
public final class TernaryInsn extends InsnNode {
|
||||
|
||||
private IfCondition condition;
|
||||
|
||||
public TernaryInsn(IfCondition condition, RegisterArg result) {
|
||||
this(condition, result, LiteralArg.TRUE, LiteralArg.FALSE);
|
||||
}
|
||||
|
||||
public TernaryInsn(IfCondition condition, RegisterArg result, InsnArg th, InsnArg els) {
|
||||
super(InsnType.TERNARY, 2);
|
||||
@@ -33,6 +39,26 @@ public class TernaryInsn extends InsnNode {
|
||||
return condition;
|
||||
}
|
||||
|
||||
public void simplifyCondition() {
|
||||
condition = IfCondition.simplify(condition);
|
||||
if (condition.getMode() == IfCondition.Mode.NOT) {
|
||||
invert();
|
||||
}
|
||||
}
|
||||
|
||||
private void invert() {
|
||||
condition = IfCondition.invert(condition);
|
||||
InsnArg tmp = getArg(0);
|
||||
setArg(0, getArg(1));
|
||||
setArg(1, tmp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getRegisterArgs(List<RegisterArg> list) {
|
||||
super.getRegisterArgs(list);
|
||||
list.addAll(condition.getRegisterArgs());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -132,7 +132,7 @@ public class ClassNode extends LineAttrNode implements ILoadable {
|
||||
try {
|
||||
new AnnotationsParser(this).parse(offset);
|
||||
} catch (DecodeException e) {
|
||||
LOG.error("Error parsing annotations in " + this, e);
|
||||
LOG.error("Error parsing annotations in {}", this, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -184,7 +184,7 @@ public class ClassNode extends LineAttrNode implements ILoadable {
|
||||
}
|
||||
}
|
||||
} catch (JadxRuntimeException e) {
|
||||
LOG.error("Class signature parse error: " + this, e);
|
||||
LOG.error("Class signature parse error: {}", this, e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,7 +198,7 @@ public class ClassNode extends LineAttrNode implements ILoadable {
|
||||
field.setType(gType);
|
||||
}
|
||||
} catch (JadxRuntimeException e) {
|
||||
LOG.error("Field signature parse error: " + field, e);
|
||||
LOG.error("Field signature parse error: {}", field, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -354,6 +354,11 @@ public class ClassNode extends LineAttrNode implements ILoadable {
|
||||
return parentClass;
|
||||
}
|
||||
|
||||
public ClassNode getTopParentClass() {
|
||||
ClassNode parent = getParentClass();
|
||||
return parent == this ? this : parent.getParentClass();
|
||||
}
|
||||
|
||||
public List<ClassNode> getInnerClasses() {
|
||||
return innerClasses;
|
||||
}
|
||||
@@ -377,10 +382,7 @@ public class ClassNode extends LineAttrNode implements ILoadable {
|
||||
|
||||
public MethodNode getDefaultConstructor() {
|
||||
for (MethodNode mth : methods) {
|
||||
if (mth.getAccessFlags().isConstructor()
|
||||
&& mth.getMethodInfo().isConstructor()
|
||||
&& (mth.getMethodInfo().getArgsCount() == 0
|
||||
|| (mth.getArguments(false) != null && mth.getArguments(false).isEmpty()))) {
|
||||
if (mth.isDefaultConstructor()) {
|
||||
return mth;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,8 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import com.android.dex.ClassData;
|
||||
import com.android.dex.ClassData.Method;
|
||||
import com.android.dex.ClassDef;
|
||||
@@ -31,16 +33,12 @@ public class DexNode {
|
||||
private final RootNode root;
|
||||
private final Dex dexBuf;
|
||||
private final List<ClassNode> classes = new ArrayList<ClassNode>();
|
||||
private final String[] strings;
|
||||
|
||||
private final Map<Object, FieldNode> constFields = new HashMap<Object, FieldNode>();
|
||||
|
||||
public DexNode(RootNode root, InputFile input) {
|
||||
this.root = root;
|
||||
this.dexBuf = input.getDexBuffer();
|
||||
|
||||
List<String> stringList = dexBuf.strings();
|
||||
this.strings = stringList.toArray(new String[stringList.size()]);
|
||||
}
|
||||
|
||||
public void loadClasses() throws DecodeException {
|
||||
@@ -53,10 +51,12 @@ public class DexNode {
|
||||
return classes;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ClassNode resolveClass(ClassInfo clsInfo) {
|
||||
return root.resolveClass(clsInfo);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public MethodNode resolveMethod(MethodInfo mth) {
|
||||
ClassNode cls = resolveClass(mth.getDeclClass());
|
||||
if (cls != null) {
|
||||
@@ -65,6 +65,7 @@ public class DexNode {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public FieldNode resolveField(FieldInfo field) {
|
||||
ClassNode cls = resolveClass(field.getDeclClass());
|
||||
if (cls != null) {
|
||||
@@ -80,7 +81,7 @@ public class DexNode {
|
||||
// DexBuffer wrappers
|
||||
|
||||
public String getString(int index) {
|
||||
return strings[index];
|
||||
return dexBuf.strings().get(index);
|
||||
}
|
||||
|
||||
public ArgType getType(int index) {
|
||||
|
||||
@@ -4,6 +4,8 @@ import jadx.core.dex.attributes.IAttributeNode;
|
||||
|
||||
public interface IContainer extends IAttributeNode {
|
||||
|
||||
// unique id for use in 'toString()' method
|
||||
/**
|
||||
* Unique id for use in 'toString()' method
|
||||
*/
|
||||
String baseString();
|
||||
}
|
||||
|
||||
@@ -35,6 +35,12 @@ public class InsnNode extends LineAttrNode {
|
||||
}
|
||||
}
|
||||
|
||||
public static InsnNode wrapArg(InsnArg arg) {
|
||||
InsnNode insn = new InsnNode(InsnType.ONE_ARG, 1);
|
||||
insn.addArg(arg);
|
||||
return insn;
|
||||
}
|
||||
|
||||
public void setResult(RegisterArg res) {
|
||||
if (res != null) {
|
||||
res.setParentInsn(this);
|
||||
@@ -101,6 +107,17 @@ public class InsnNode extends LineAttrNode {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected boolean removeArg(InsnArg arg) {
|
||||
int count = getArgsCount();
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (arg == arguments.get(i)) {
|
||||
arguments.remove(i);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected void addReg(DecodedInstruction insn, int i, ArgType type) {
|
||||
addArg(InsnArg.reg(insn, i, type));
|
||||
}
|
||||
@@ -135,6 +152,18 @@ public class InsnNode extends LineAttrNode {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isConstInsn() {
|
||||
switch (getType()) {
|
||||
case CONST:
|
||||
case CONST_STR:
|
||||
case CONST_CLASS:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean canReorder() {
|
||||
switch (getType()) {
|
||||
case CONST:
|
||||
@@ -153,7 +182,6 @@ public class InsnNode extends LineAttrNode {
|
||||
case NEW_ARRAY:
|
||||
case NEW_MULTIDIM_ARRAY:
|
||||
case STR_CONCAT:
|
||||
case MOVE_EXCEPTION:
|
||||
return true;
|
||||
|
||||
default:
|
||||
|
||||
@@ -15,9 +15,9 @@ import jadx.core.dex.instructions.InsnDecoder;
|
||||
import jadx.core.dex.instructions.SwitchNode;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.MthParameterArg;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.instructions.args.SSAVar;
|
||||
import jadx.core.dex.instructions.args.TypeImmutableArg;
|
||||
import jadx.core.dex.nodes.parser.SignatureParser;
|
||||
import jadx.core.dex.regions.Region;
|
||||
import jadx.core.dex.trycatch.ExcHandlerAttr;
|
||||
@@ -34,6 +34,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -52,6 +53,7 @@ public class MethodNode extends LineAttrNode implements ILoadable {
|
||||
private final Method methodData;
|
||||
private int regsCount;
|
||||
private InsnNode[] instructions;
|
||||
private int codeSize;
|
||||
private int debugInfoOffset;
|
||||
private boolean noCode;
|
||||
|
||||
@@ -82,6 +84,7 @@ public class MethodNode extends LineAttrNode implements ILoadable {
|
||||
try {
|
||||
if (noCode) {
|
||||
regsCount = 0;
|
||||
codeSize = 0;
|
||||
initMethodTypes();
|
||||
return;
|
||||
}
|
||||
@@ -94,6 +97,7 @@ public class MethodNode extends LineAttrNode implements ILoadable {
|
||||
InsnDecoder decoder = new InsnDecoder(this);
|
||||
decoder.decodeInsns(mthCode);
|
||||
instructions = decoder.process();
|
||||
codeSize = instructions.length;
|
||||
|
||||
initTryCatches(mthCode);
|
||||
initJumps();
|
||||
@@ -144,8 +148,7 @@ public class MethodNode extends LineAttrNode implements ILoadable {
|
||||
return false;
|
||||
}
|
||||
if (!mthInfo.isConstructor()) {
|
||||
LOG.warn("Wrong signature parse result: " + sp + " -> " + argsTypes
|
||||
+ ", not generic version: " + mthArgs);
|
||||
LOG.warn("Wrong signature parse result: {} -> {}, not generic version: {}", sp, argsTypes, mthArgs);
|
||||
return false;
|
||||
} else if (getParentClass().getAccessFlags().isEnum()) {
|
||||
// TODO:
|
||||
@@ -161,7 +164,7 @@ public class MethodNode extends LineAttrNode implements ILoadable {
|
||||
}
|
||||
initArguments(argsTypes);
|
||||
} catch (JadxRuntimeException e) {
|
||||
LOG.error("Method signature parse error: " + this, e);
|
||||
LOG.error("Method signature parse error: {}", this, e);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -180,7 +183,7 @@ public class MethodNode extends LineAttrNode implements ILoadable {
|
||||
if (accFlags.isStatic()) {
|
||||
thisArg = null;
|
||||
} else {
|
||||
MthParameterArg arg = InsnArg.parameterReg(pos - 1, parentClass.getClassInfo().getType());
|
||||
TypeImmutableArg arg = InsnArg.typeImmutableReg(pos - 1, parentClass.getClassInfo().getType());
|
||||
arg.markAsThis();
|
||||
thisArg = arg;
|
||||
}
|
||||
@@ -190,7 +193,7 @@ public class MethodNode extends LineAttrNode implements ILoadable {
|
||||
}
|
||||
argsList = new ArrayList<RegisterArg>(args.size());
|
||||
for (ArgType arg : args) {
|
||||
argsList.add(InsnArg.parameterReg(pos, arg));
|
||||
argsList.add(InsnArg.typeImmutableReg(pos, arg));
|
||||
pos += arg.getRegCount();
|
||||
}
|
||||
}
|
||||
@@ -201,9 +204,8 @@ public class MethodNode extends LineAttrNode implements ILoadable {
|
||||
list.add(thisArg);
|
||||
list.addAll(argsList);
|
||||
return list;
|
||||
} else {
|
||||
return argsList;
|
||||
}
|
||||
return argsList;
|
||||
}
|
||||
|
||||
public RegisterArg removeFirstArgument() {
|
||||
@@ -227,6 +229,9 @@ public class MethodNode extends LineAttrNode implements ILoadable {
|
||||
InsnNode[] insnByOffset = instructions;
|
||||
CatchHandler[] catchBlocks = mthCode.getCatchHandlers();
|
||||
Try[] tries = mthCode.getTries();
|
||||
if (catchBlocks.length == 0 && tries.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
int hc = 0;
|
||||
Set<Integer> addrs = new HashSet<Integer>();
|
||||
@@ -259,6 +264,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -270,7 +276,6 @@ public class MethodNode extends LineAttrNode implements ILoadable {
|
||||
for (TryCatchBlock ct : catches) {
|
||||
for (ExceptionHandler eh : ct.getHandlers()) {
|
||||
int addr = eh.getHandleOffset();
|
||||
// assert addrs.add(addr) : "Instruction already contains EXC_HANDLER attribute";
|
||||
ExcHandlerAttr ehAttr = new ExcHandlerAttr(ct, eh);
|
||||
insnByOffset[addr].addAttr(ehAttr);
|
||||
}
|
||||
@@ -283,13 +288,17 @@ public class MethodNode extends LineAttrNode implements ILoadable {
|
||||
int offset = aTry.getStartAddress();
|
||||
int end = offset + aTry.getInstructionCount() - 1;
|
||||
|
||||
insnByOffset[offset].add(AFlag.TRY_ENTER);
|
||||
InsnNode insn = insnByOffset[offset];
|
||||
insn.add(AFlag.TRY_ENTER);
|
||||
while (offset <= end && offset >= 0) {
|
||||
catchBlock.addInsn(insnByOffset[offset]);
|
||||
insn = insnByOffset[offset];
|
||||
catchBlock.addInsn(insn);
|
||||
offset = InsnDecoder.getNextInsnOffset(insnByOffset, offset);
|
||||
}
|
||||
if (insnByOffset[end] != null) {
|
||||
insnByOffset[end].add(AFlag.TRY_LEAVE);
|
||||
} else {
|
||||
insn.add(AFlag.TRY_LEAVE);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -302,18 +311,17 @@ public class MethodNode extends LineAttrNode implements ILoadable {
|
||||
continue;
|
||||
}
|
||||
switch (insn.getType()) {
|
||||
case SWITCH: {
|
||||
case SWITCH:
|
||||
SwitchNode sw = (SwitchNode) insn;
|
||||
for (int target : sw.getTargets()) {
|
||||
addJump(insnByOffset, offset, target);
|
||||
}
|
||||
// default case
|
||||
int next = InsnDecoder.getNextInsnOffset(insnByOffset, offset);
|
||||
if (next != -1) {
|
||||
addJump(insnByOffset, offset, next);
|
||||
int nextInsnOffset = InsnDecoder.getNextInsnOffset(insnByOffset, offset);
|
||||
if (nextInsnOffset != -1) {
|
||||
addJump(insnByOffset, offset, nextInsnOffset);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case IF:
|
||||
int next = InsnDecoder.getNextInsnOffset(insnByOffset, offset);
|
||||
@@ -338,12 +346,7 @@ public class MethodNode extends LineAttrNode implements ILoadable {
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
String name = mthInfo.getName();
|
||||
if (name.equals(parentClass.getShortName())) {
|
||||
return name + "_";
|
||||
} else {
|
||||
return name;
|
||||
}
|
||||
return mthInfo.getName();
|
||||
}
|
||||
|
||||
public ClassNode getParentClass() {
|
||||
@@ -354,6 +357,10 @@ public class MethodNode extends LineAttrNode implements ILoadable {
|
||||
return noCode;
|
||||
}
|
||||
|
||||
public int getCodeSize() {
|
||||
return codeSize;
|
||||
}
|
||||
|
||||
public InsnNode[] getInstructions() {
|
||||
return instructions;
|
||||
}
|
||||
@@ -403,10 +410,14 @@ public class MethodNode extends LineAttrNode implements ILoadable {
|
||||
if (loops.isEmpty()) {
|
||||
loops = new ArrayList<LoopInfo>(5);
|
||||
}
|
||||
loop.setId(loops.size());
|
||||
loops.add(loop);
|
||||
}
|
||||
|
||||
public LoopInfo getLoopForBlock(BlockNode block) {
|
||||
if (loops.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
for (LoopInfo loop : loops) {
|
||||
if (loop.getLoopBlocks().contains(block)) {
|
||||
return loop;
|
||||
@@ -415,10 +426,27 @@ public class MethodNode extends LineAttrNode implements ILoadable {
|
||||
return null;
|
||||
}
|
||||
|
||||
public List<LoopInfo> getAllLoopsForBlock(BlockNode block) {
|
||||
if (loops.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<LoopInfo> list = new ArrayList<LoopInfo>(loops.size());
|
||||
for (LoopInfo loop : loops) {
|
||||
if (loop.getLoopBlocks().contains(block)) {
|
||||
list.add(loop);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
public int getLoopsCount() {
|
||||
return loops.size();
|
||||
}
|
||||
|
||||
public Iterable<LoopInfo> getLoops() {
|
||||
return loops;
|
||||
}
|
||||
|
||||
public ExceptionHandler addExceptionHandler(ExceptionHandler handler) {
|
||||
if (exceptionHandlers.isEmpty()) {
|
||||
exceptionHandlers = new ArrayList<ExceptionHandler>(2);
|
||||
@@ -441,6 +469,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
|
||||
*/
|
||||
@@ -462,6 +494,24 @@ public class MethodNode extends LineAttrNode implements ILoadable {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isDefaultConstructor() {
|
||||
boolean result = false;
|
||||
if (accFlags.isConstructor() && mthInfo.isConstructor()) {
|
||||
int defaultArgCount = 0;
|
||||
/** workaround for non-static inner class constructor, that has synthetic argument */
|
||||
if (parentClass.getClassInfo().isInner()
|
||||
&& !parentClass.getAccessFlags().isStatic()) {
|
||||
ClassNode outerCls = parentClass.getParentClass();
|
||||
if (argsList != null && !argsList.isEmpty()
|
||||
&& argsList.get(0).getType().equals(outerCls.getClassInfo().getType())) {
|
||||
defaultArgCount = 1;
|
||||
}
|
||||
}
|
||||
result = (argsList == null) || (argsList.size() == defaultArgCount);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public int getRegsCount() {
|
||||
return regsCount;
|
||||
}
|
||||
@@ -470,7 +520,7 @@ public class MethodNode extends LineAttrNode implements ILoadable {
|
||||
return debugInfoOffset;
|
||||
}
|
||||
|
||||
public SSAVar makeNewSVar(int regNum, int[] versions, RegisterArg arg) {
|
||||
public SSAVar makeNewSVar(int regNum, int[] versions, @NotNull RegisterArg arg) {
|
||||
SSAVar var = new SSAVar(regNum, versions[regNum], arg);
|
||||
versions[regNum]++;
|
||||
if (sVars.isEmpty()) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -3,6 +3,7 @@ package jadx.core.dex.nodes.parser;
|
||||
import jadx.core.dex.attributes.nodes.SourceFileAttr;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.instructions.args.SSAVar;
|
||||
import jadx.core.dex.nodes.DexNode;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
@@ -112,8 +113,9 @@ public class DebugInfoParser {
|
||||
int regNum = section.readUleb128();
|
||||
LocalVar var = locals[regNum];
|
||||
if (var != null) {
|
||||
var.end(addr, line);
|
||||
setVar(var);
|
||||
if (var.end(addr, line)) {
|
||||
setVar(var);
|
||||
}
|
||||
var.start(addr, line);
|
||||
}
|
||||
break;
|
||||
@@ -160,7 +162,7 @@ public class DebugInfoParser {
|
||||
|
||||
for (LocalVar var : locals) {
|
||||
if (var != null && !var.isEnd()) {
|
||||
var.end(addr, line);
|
||||
var.end(mth.getCodeSize() - 1, line);
|
||||
setVar(var);
|
||||
}
|
||||
}
|
||||
@@ -169,6 +171,8 @@ public class DebugInfoParser {
|
||||
|
||||
private int addrChange(int addr, int addrInc, int line) {
|
||||
int newAddr = addr + addrInc;
|
||||
int maxAddr = insnByOffset.length - 1;
|
||||
newAddr = Math.min(newAddr, maxAddr);
|
||||
for (int i = addr + 1; i <= newAddr; i++) {
|
||||
InsnNode insn = insnByOffset[i];
|
||||
if (insn == null) {
|
||||
@@ -236,7 +240,27 @@ public class DebugInfoParser {
|
||||
if (arg != null && arg.isRegister()) {
|
||||
RegisterArg reg = (RegisterArg) arg;
|
||||
if (var.getRegNum() == reg.getRegNum()) {
|
||||
reg.mergeDebugInfo(var.getType(), var.getName());
|
||||
SSAVar ssaVar = reg.getSVar();
|
||||
|
||||
boolean mergeRequired = false;
|
||||
|
||||
if (ssaVar != null) {
|
||||
int ssaEnd = ssaVar.getEndAddr();
|
||||
int ssaStart = ssaVar.getStartAddr();
|
||||
int localStart = var.getStartAddr();
|
||||
int localEnd = var.getEndAddr();
|
||||
|
||||
boolean isIntersected = !((localEnd < ssaStart) || (ssaEnd < localStart));
|
||||
if (isIntersected && (ssaEnd <= localEnd)) {
|
||||
mergeRequired = true;
|
||||
}
|
||||
} else {
|
||||
mergeRequired = true;
|
||||
}
|
||||
|
||||
if (mergeRequired) {
|
||||
reg.mergeDebugInfo(var.getType(), var.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -21,9 +21,9 @@ final class LocalVar {
|
||||
|
||||
public LocalVar(DexNode dex, int rn, int nameId, int typeId, int signId) {
|
||||
this.regNum = rn;
|
||||
String name = (nameId == DexNode.NO_INDEX ? null : dex.getString(nameId));
|
||||
ArgType type = (typeId == DexNode.NO_INDEX ? null : dex.getType(typeId));
|
||||
String sign = (signId == DexNode.NO_INDEX ? null : dex.getString(signId));
|
||||
String name = nameId == DexNode.NO_INDEX ? null : dex.getString(nameId);
|
||||
ArgType type = typeId == DexNode.NO_INDEX ? null : dex.getType(typeId);
|
||||
String sign = signId == DexNode.NO_INDEX ? null : dex.getString(signId);
|
||||
|
||||
init(name, type, sign);
|
||||
}
|
||||
@@ -41,7 +41,7 @@ final class LocalVar {
|
||||
type = gType;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.error("Can't parse signature for local variable: " + sign, e);
|
||||
LOG.error("Can't parse signature for local variable: {}", sign, e);
|
||||
}
|
||||
}
|
||||
this.name = name;
|
||||
@@ -56,10 +56,8 @@ final class LocalVar {
|
||||
LOG.warn("Generic type in debug info not equals: {} != {}", type, gType);
|
||||
}
|
||||
apply = true;
|
||||
} else if (el.isGenericType()) {
|
||||
apply = true;
|
||||
} else {
|
||||
apply = false;
|
||||
apply = el.isGenericType();
|
||||
}
|
||||
return apply;
|
||||
}
|
||||
@@ -69,9 +67,20 @@ final class LocalVar {
|
||||
this.startAddr = addr;
|
||||
}
|
||||
|
||||
public void end(int addr, int line) {
|
||||
this.isEnd = true;
|
||||
this.endAddr = addr;
|
||||
/**
|
||||
* Sets end address of local variable
|
||||
*
|
||||
* @param addr address
|
||||
* @param line source line
|
||||
* @return <b>true</b> if local variable was active, else <b>false</b>
|
||||
*/
|
||||
public boolean end(int addr, int line) {
|
||||
if (!isEnd) {
|
||||
this.isEnd = true;
|
||||
this.endAddr = addr;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public int getRegNum() {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
package jadx.core.dex.regions;
|
||||
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
|
||||
public final class IfInfo {
|
||||
IfCondition condition;
|
||||
BlockNode ifnode;
|
||||
BlockNode thenBlock;
|
||||
BlockNode elseBlock;
|
||||
|
||||
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 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;
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
package jadx.core.dex.regions;
|
||||
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.dex.nodes.IBlock;
|
||||
import jadx.core.dex.nodes.IContainer;
|
||||
import jadx.core.dex.nodes.IRegion;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public final class TernaryRegion extends AbstractRegion {
|
||||
private final IBlock container;
|
||||
|
||||
public TernaryRegion(IRegion parent, BlockNode block) {
|
||||
super(parent);
|
||||
this.container = block;
|
||||
}
|
||||
|
||||
public IBlock getBlock() {
|
||||
return container;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<IContainer> getSubBlocks() {
|
||||
return Collections.singletonList((IContainer) container);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String baseString() {
|
||||
return container.baseString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TERN:" + container;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package jadx.core.dex.regions;
|
||||
|
||||
import jadx.core.dex.nodes.IContainer;
|
||||
import jadx.core.dex.nodes.IRegion;
|
||||
import jadx.core.dex.trycatch.ExceptionHandler;
|
||||
import jadx.core.dex.trycatch.TryCatchBlock;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public final class TryCatchRegion extends AbstractRegion {
|
||||
|
||||
private final IContainer tryRegion;
|
||||
private List<IContainer> catchRegions = Collections.emptyList();
|
||||
private TryCatchBlock tryCatchBlock;
|
||||
|
||||
public TryCatchRegion(IRegion parent, IContainer tryRegion) {
|
||||
super(parent);
|
||||
this.tryRegion = tryRegion;
|
||||
}
|
||||
|
||||
public IContainer getTryRegion() {
|
||||
return tryRegion;
|
||||
}
|
||||
|
||||
public List<IContainer> getCatchRegions() {
|
||||
return catchRegions;
|
||||
}
|
||||
|
||||
public TryCatchBlock geTryCatchBlock() {
|
||||
return tryCatchBlock;
|
||||
}
|
||||
|
||||
public void setTryCatchBlock(TryCatchBlock tryCatchBlock) {
|
||||
this.tryCatchBlock = tryCatchBlock;
|
||||
this.catchRegions = new ArrayList<IContainer>(tryCatchBlock.getHandlersCount());
|
||||
for (ExceptionHandler handler : tryCatchBlock.getHandlers()) {
|
||||
catchRegions.add(handler.getHandlerRegion());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<IContainer> getSubBlocks() {
|
||||
List<IContainer> all = new ArrayList<IContainer>(1 + catchRegions.size());
|
||||
all.add(tryRegion);
|
||||
all.addAll(catchRegions);
|
||||
return Collections.unmodifiableList(all);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String baseString() {
|
||||
return tryRegion.baseString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Try: " + tryRegion
|
||||
+ " catches: " + Utils.listToString(catchRegions);
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package jadx.core.dex.regions;
|
||||
package jadx.core.dex.regions.conditions;
|
||||
|
||||
import jadx.core.dex.instructions.IfNode;
|
||||
import jadx.core.dex.instructions.IfOp;
|
||||
+21
-18
@@ -1,9 +1,8 @@
|
||||
package jadx.core.dex.regions;
|
||||
package jadx.core.dex.regions.conditions;
|
||||
|
||||
import jadx.core.dex.instructions.IfNode;
|
||||
import jadx.core.dex.instructions.IfOp;
|
||||
import jadx.core.dex.instructions.InsnType;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.InsnWrapArg;
|
||||
import jadx.core.dex.instructions.args.LiteralArg;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
@@ -20,8 +19,9 @@ import java.util.List;
|
||||
|
||||
public final class IfCondition {
|
||||
|
||||
public static enum Mode {
|
||||
public enum Mode {
|
||||
COMPARE,
|
||||
TERNARY,
|
||||
NOT,
|
||||
AND,
|
||||
OR
|
||||
@@ -64,18 +64,17 @@ public final class IfCondition {
|
||||
return new IfCondition(new Compare(insn));
|
||||
}
|
||||
|
||||
public static IfCondition ternary(IfCondition a, IfCondition b, IfCondition c) {
|
||||
return new IfCondition(Mode.TERNARY, Arrays.asList(a, b, c));
|
||||
}
|
||||
|
||||
public static IfCondition merge(Mode mode, IfCondition a, IfCondition b) {
|
||||
if (a.getMode() == mode) {
|
||||
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() {
|
||||
@@ -94,6 +93,10 @@ public final class IfCondition {
|
||||
return args.get(1);
|
||||
}
|
||||
|
||||
public IfCondition third() {
|
||||
return args.get(2);
|
||||
}
|
||||
|
||||
public void addArg(IfCondition c) {
|
||||
args.add(c);
|
||||
}
|
||||
@@ -111,6 +114,8 @@ public final class IfCondition {
|
||||
switch (mode) {
|
||||
case COMPARE:
|
||||
return new IfCondition(cond.getCompare().invert());
|
||||
case TERNARY:
|
||||
return ternary(not(cond.first()), cond.third(), cond.second());
|
||||
case NOT:
|
||||
return cond.first();
|
||||
case AND:
|
||||
@@ -159,7 +164,10 @@ public final class IfCondition {
|
||||
cond = new IfCondition(cond.getMode(), args);
|
||||
}
|
||||
if (cond.getMode() == Mode.NOT && cond.first().getMode() == Mode.NOT) {
|
||||
cond = cond.first().first();
|
||||
cond = invert(cond.first());
|
||||
}
|
||||
if (cond.getMode() == Mode.TERNARY && cond.first().getMode() == Mode.NOT) {
|
||||
cond = invert(cond);
|
||||
}
|
||||
|
||||
// for condition with a lot of negations => make invert
|
||||
@@ -200,14 +208,7 @@ public final class IfCondition {
|
||||
public List<RegisterArg> getRegisterArgs() {
|
||||
List<RegisterArg> list = new LinkedList<RegisterArg>();
|
||||
if (mode == Mode.COMPARE) {
|
||||
InsnArg a = compare.getA();
|
||||
if (a.isRegister()) {
|
||||
list.add((RegisterArg) a);
|
||||
}
|
||||
InsnArg b = compare.getB();
|
||||
if (b.isRegister()) {
|
||||
list.add((RegisterArg) b);
|
||||
}
|
||||
compare.getInsn().getRegisterArgs(list);
|
||||
} else {
|
||||
for (IfCondition arg : args) {
|
||||
list.addAll(arg.getRegisterArgs());
|
||||
@@ -221,6 +222,8 @@ public final class IfCondition {
|
||||
switch (mode) {
|
||||
case COMPARE:
|
||||
return compare.toString();
|
||||
case TERNARY:
|
||||
return first() + " ? " + second() + " : " + third();
|
||||
case NOT:
|
||||
return "!" + first();
|
||||
case AND:
|
||||
@@ -0,0 +1,95 @@
|
||||
package jadx.core.dex.regions.conditions;
|
||||
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public final class IfInfo {
|
||||
private final IfCondition condition;
|
||||
private final Set<BlockNode> mergedBlocks;
|
||||
private final BlockNode thenBlock;
|
||||
private final BlockNode elseBlock;
|
||||
private final Set<BlockNode> skipBlocks;
|
||||
private BlockNode outBlock;
|
||||
@Deprecated
|
||||
private BlockNode ifBlock;
|
||||
|
||||
public IfInfo(IfCondition condition, BlockNode thenBlock, BlockNode elseBlock) {
|
||||
this(condition, thenBlock, elseBlock, new HashSet<BlockNode>(), new HashSet<BlockNode>());
|
||||
}
|
||||
|
||||
public IfInfo(IfCondition condition, IfInfo info) {
|
||||
this(condition, info.getThenBlock(), info.getElseBlock(), info.getMergedBlocks(), info.getSkipBlocks());
|
||||
}
|
||||
|
||||
public IfInfo(IfInfo info, BlockNode thenBlock, BlockNode elseBlock) {
|
||||
this(info.getCondition(), thenBlock, elseBlock, info.getMergedBlocks(), info.getSkipBlocks());
|
||||
}
|
||||
|
||||
private IfInfo(IfCondition condition, BlockNode thenBlock, BlockNode elseBlock,
|
||||
Set<BlockNode> mergedBlocks, Set<BlockNode> skipBlocks) {
|
||||
this.condition = condition;
|
||||
this.thenBlock = thenBlock;
|
||||
this.elseBlock = elseBlock;
|
||||
this.mergedBlocks = mergedBlocks;
|
||||
this.skipBlocks = skipBlocks;
|
||||
}
|
||||
|
||||
public static IfInfo invert(IfInfo info) {
|
||||
IfCondition invertedCondition = IfCondition.invert(info.getCondition());
|
||||
IfInfo tmpIf = new IfInfo(invertedCondition,
|
||||
info.getElseBlock(), info.getThenBlock(),
|
||||
info.getMergedBlocks(), info.getSkipBlocks());
|
||||
tmpIf.setIfBlock(info.getIfBlock());
|
||||
return tmpIf;
|
||||
}
|
||||
|
||||
public void merge(IfInfo... arr) {
|
||||
for (IfInfo info : arr) {
|
||||
mergedBlocks.addAll(info.getMergedBlocks());
|
||||
skipBlocks.addAll(info.getSkipBlocks());
|
||||
}
|
||||
}
|
||||
|
||||
public IfCondition getCondition() {
|
||||
return condition;
|
||||
}
|
||||
|
||||
public Set<BlockNode> getMergedBlocks() {
|
||||
return mergedBlocks;
|
||||
}
|
||||
|
||||
public Set<BlockNode> getSkipBlocks() {
|
||||
return skipBlocks;
|
||||
}
|
||||
|
||||
public BlockNode getThenBlock() {
|
||||
return thenBlock;
|
||||
}
|
||||
|
||||
public BlockNode getElseBlock() {
|
||||
return 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;
|
||||
}
|
||||
}
|
||||
+16
-21
@@ -1,8 +1,9 @@
|
||||
package jadx.core.dex.regions;
|
||||
package jadx.core.dex.regions.conditions;
|
||||
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.dex.nodes.IContainer;
|
||||
import jadx.core.dex.nodes.IRegion;
|
||||
import jadx.core.dex.regions.AbstractRegion;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
@@ -16,8 +17,6 @@ public final class IfRegion extends AbstractRegion {
|
||||
private IContainer thenRegion;
|
||||
private IContainer elseRegion;
|
||||
|
||||
private TernaryRegion ternRegion;
|
||||
|
||||
public IfRegion(IRegion parent, BlockNode header) {
|
||||
super(parent);
|
||||
assert header.getInstructions().size() == 1;
|
||||
@@ -53,14 +52,6 @@ public final class IfRegion extends AbstractRegion {
|
||||
return header;
|
||||
}
|
||||
|
||||
public void setTernRegion(TernaryRegion ternRegion) {
|
||||
this.ternRegion = ternRegion;
|
||||
}
|
||||
|
||||
public TernaryRegion getTernRegion() {
|
||||
return ternRegion;
|
||||
}
|
||||
|
||||
public boolean simplifyCondition() {
|
||||
IfCondition cond = IfCondition.simplify(condition);
|
||||
if (cond != condition) {
|
||||
@@ -87,10 +78,7 @@ public final class IfRegion extends AbstractRegion {
|
||||
|
||||
@Override
|
||||
public List<IContainer> getSubBlocks() {
|
||||
if (ternRegion != null) {
|
||||
return ternRegion.getSubBlocks();
|
||||
}
|
||||
ArrayList<IContainer> all = new ArrayList<IContainer>(3);
|
||||
List<IContainer> all = new ArrayList<IContainer>(3);
|
||||
all.add(header);
|
||||
if (thenRegion != null) {
|
||||
all.add(thenRegion);
|
||||
@@ -102,10 +90,20 @@ public final class IfRegion extends AbstractRegion {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String baseString() {
|
||||
if (ternRegion != null) {
|
||||
return ternRegion.baseString();
|
||||
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() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (thenRegion != null) {
|
||||
sb.append(thenRegion.baseString());
|
||||
@@ -118,9 +116,6 @@ public final class IfRegion extends AbstractRegion {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (ternRegion != null) {
|
||||
return ternRegion.toString();
|
||||
}
|
||||
return "IF(" + condition + ") then " + thenRegion + " else " + elseRegion;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package jadx.core.dex.regions.loops;
|
||||
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
|
||||
public final class ForEachLoop extends LoopType {
|
||||
private final RegisterArg varArg;
|
||||
private final InsnArg iterableArg;
|
||||
|
||||
public ForEachLoop(RegisterArg varArg, InsnArg iterableArg) {
|
||||
this.varArg = varArg;
|
||||
this.iterableArg = iterableArg;
|
||||
}
|
||||
|
||||
public RegisterArg getVarArg() {
|
||||
return varArg;
|
||||
}
|
||||
|
||||
public InsnArg getIterableArg() {
|
||||
return iterableArg;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package jadx.core.dex.regions.loops;
|
||||
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
|
||||
public final class ForLoop extends LoopType {
|
||||
|
||||
private final InsnNode initInsn;
|
||||
private final InsnNode incrInsn;
|
||||
|
||||
public ForLoop(InsnNode initInsn, InsnNode incrInsn) {
|
||||
this.initInsn = initInsn;
|
||||
this.incrInsn = incrInsn;
|
||||
}
|
||||
|
||||
public InsnNode getInitInsn() {
|
||||
return initInsn;
|
||||
}
|
||||
|
||||
public InsnNode getIncrInsn() {
|
||||
return incrInsn;
|
||||
}
|
||||
}
|
||||
+49
-24
@@ -1,11 +1,14 @@
|
||||
package jadx.core.dex.regions;
|
||||
package jadx.core.dex.regions.loops;
|
||||
|
||||
import jadx.core.dex.attributes.nodes.LoopInfo;
|
||||
import jadx.core.dex.instructions.IfNode;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.dex.nodes.IContainer;
|
||||
import jadx.core.dex.nodes.IRegion;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.regions.AbstractRegion;
|
||||
import jadx.core.dex.regions.conditions.IfCondition;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
@@ -13,21 +16,29 @@ import java.util.List;
|
||||
|
||||
public final class LoopRegion extends AbstractRegion {
|
||||
|
||||
private final LoopInfo info;
|
||||
// loop header contains one 'if' insn, equals null for infinite loop
|
||||
private IfCondition condition;
|
||||
private final BlockNode conditionBlock;
|
||||
// instruction which must be executed before condition in every loop
|
||||
private BlockNode preCondition;
|
||||
private IContainer body;
|
||||
private IRegion body;
|
||||
private final boolean conditionAtEnd;
|
||||
|
||||
public LoopRegion(IRegion parent, BlockNode header, boolean reversed) {
|
||||
private LoopType type;
|
||||
|
||||
public LoopRegion(IRegion parent, LoopInfo info, BlockNode header, boolean reversed) {
|
||||
super(parent);
|
||||
this.info = info;
|
||||
this.conditionBlock = header;
|
||||
this.condition = IfCondition.fromIfBlock(header);
|
||||
this.conditionAtEnd = reversed;
|
||||
}
|
||||
|
||||
public LoopInfo getInfo() {
|
||||
return info;
|
||||
}
|
||||
|
||||
public IfCondition getCondition() {
|
||||
return condition;
|
||||
}
|
||||
@@ -40,11 +51,11 @@ public final class LoopRegion extends AbstractRegion {
|
||||
return conditionBlock;
|
||||
}
|
||||
|
||||
public IContainer getBody() {
|
||||
public IRegion getBody() {
|
||||
return body;
|
||||
}
|
||||
|
||||
public void setBody(IContainer body) {
|
||||
public void setBody(IRegion body) {
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
@@ -77,25 +88,24 @@ public final class LoopRegion extends AbstractRegion {
|
||||
InsnNode insn = insns.get(i);
|
||||
if (insn.getResult() == null) {
|
||||
return false;
|
||||
} else {
|
||||
RegisterArg res = insn.getResult();
|
||||
if (res.getSVar().getUseCount() > 1) {
|
||||
return false;
|
||||
}
|
||||
boolean found = false;
|
||||
// search result arg in other insns
|
||||
for (int j = i + 1; j < size; j++) {
|
||||
if (insns.get(i).containsArg(res)) {
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
// or in if insn
|
||||
if (!found && ifInsn.containsArg(res)) {
|
||||
}
|
||||
RegisterArg res = insn.getResult();
|
||||
if (res.getSVar().getUseCount() > 1) {
|
||||
return false;
|
||||
}
|
||||
boolean found = false;
|
||||
// search result arg in other insns
|
||||
for (int j = i + 1; j < size; j++) {
|
||||
if (insns.get(i).containsArg(res)) {
|
||||
found = true;
|
||||
}
|
||||
if (!found) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// or in if insn
|
||||
if (!found && ifInsn.containsArg(res)) {
|
||||
found = true;
|
||||
}
|
||||
if (!found) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
@@ -116,6 +126,14 @@ public final class LoopRegion extends AbstractRegion {
|
||||
}
|
||||
}
|
||||
|
||||
public LoopType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(LoopType type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<IContainer> getSubBlocks() {
|
||||
List<IContainer> all = new ArrayList<IContainer>(3);
|
||||
@@ -125,10 +143,17 @@ 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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean replaceSubBlock(IContainer oldBlock, IContainer newBlock) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String baseString() {
|
||||
return body.baseString();
|
||||
@@ -136,6 +161,6 @@ public final class LoopRegion extends AbstractRegion {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "LOOP";
|
||||
return "LOOP:" + info.getId() + ": " + baseString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package jadx.core.dex.regions.loops;
|
||||
|
||||
public abstract class LoopType {
|
||||
}
|
||||
@@ -2,7 +2,7 @@ package jadx.core.dex.trycatch;
|
||||
|
||||
import jadx.core.Consts;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.instructions.args.NamedArg;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.dex.nodes.IContainer;
|
||||
import jadx.core.utils.InsnUtils;
|
||||
@@ -18,7 +18,7 @@ public class ExceptionHandler {
|
||||
private BlockNode handlerBlock;
|
||||
private final List<BlockNode> blocks = new ArrayList<BlockNode>();
|
||||
private IContainer handlerRegion;
|
||||
private NamedArg arg;
|
||||
private InsnArg arg;
|
||||
|
||||
private TryCatchBlock tryBlock;
|
||||
|
||||
@@ -63,11 +63,11 @@ public class ExceptionHandler {
|
||||
this.handlerRegion = handlerRegion;
|
||||
}
|
||||
|
||||
public NamedArg getArg() {
|
||||
public InsnArg getArg() {
|
||||
return arg;
|
||||
}
|
||||
|
||||
public void setArg(NamedArg arg) {
|
||||
public void setArg(InsnArg arg) {
|
||||
this.arg = arg;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@ import jadx.core.dex.trycatch.CatchAttr;
|
||||
import jadx.core.dex.trycatch.ExceptionHandler;
|
||||
import jadx.core.dex.trycatch.SplitterBlockAttr;
|
||||
import jadx.core.utils.BlockUtils;
|
||||
import jadx.core.utils.EmptyBitSet;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -28,6 +27,8 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static jadx.core.utils.EmptyBitSet.EMPTY;
|
||||
|
||||
public class BlockMakerVisitor extends AbstractVisitor {
|
||||
|
||||
// leave these instructions alone in block node
|
||||
@@ -36,9 +37,8 @@ public class BlockMakerVisitor extends AbstractVisitor {
|
||||
InsnType.IF,
|
||||
InsnType.SWITCH,
|
||||
InsnType.MONITOR_ENTER,
|
||||
InsnType.MONITOR_EXIT);
|
||||
|
||||
private static final BitSet EMPTY_BITSET = new EmptyBitSet();
|
||||
InsnType.MONITOR_EXIT
|
||||
);
|
||||
|
||||
@Override
|
||||
public void visit(MethodNode mth) {
|
||||
@@ -103,7 +103,9 @@ public class BlockMakerVisitor extends AbstractVisitor {
|
||||
// add this insn in new block
|
||||
block = startNewBlock(mth, -1);
|
||||
curBlock.add(AFlag.SYNTHETIC);
|
||||
block.addAttr(new SplitterBlockAttr(curBlock));
|
||||
SplitterBlockAttr splitter = new SplitterBlockAttr(curBlock);
|
||||
block.addAttr(splitter);
|
||||
curBlock.addAttr(splitter);
|
||||
connect(curBlock, block);
|
||||
curBlock = block;
|
||||
} else {
|
||||
@@ -131,12 +133,16 @@ public class BlockMakerVisitor extends AbstractVisitor {
|
||||
// get synthetic block for handlers
|
||||
SplitterBlockAttr spl = block.get(AType.SPLITTER_BLOCK);
|
||||
if (catches != null && spl != null) {
|
||||
BlockNode connBlock = spl.getBlock();
|
||||
BlockNode splitterBlock = spl.getBlock();
|
||||
boolean tryEnd = insn.contains(AFlag.TRY_LEAVE);
|
||||
for (ExceptionHandler h : catches.getTryBlock().getHandlers()) {
|
||||
BlockNode destBlock = getBlock(h.getHandleOffset(), blocksMap);
|
||||
BlockNode handlerBlock = getBlock(h.getHandleOffset(), blocksMap);
|
||||
// skip self loop in handler
|
||||
if (connBlock != destBlock) {
|
||||
connect(connBlock, destBlock);
|
||||
if (splitterBlock != handlerBlock) {
|
||||
connect(splitterBlock, handlerBlock);
|
||||
}
|
||||
if (tryEnd) {
|
||||
connect(block, handlerBlock);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -190,6 +196,7 @@ public class BlockMakerVisitor extends AbstractVisitor {
|
||||
}
|
||||
computeDominanceFrontier(mth);
|
||||
registerLoops(mth);
|
||||
processNestedLoops(mth);
|
||||
}
|
||||
|
||||
private static BlockNode getBlock(int offset, Map<Integer, BlockNode> blocksMap) {
|
||||
@@ -291,7 +298,7 @@ public class BlockMakerVisitor extends AbstractVisitor {
|
||||
|
||||
private static void computeDominanceFrontier(MethodNode mth) {
|
||||
for (BlockNode exit : mth.getExitBlocks()) {
|
||||
exit.setDomFrontier(EMPTY_BITSET);
|
||||
exit.setDomFrontier(EMPTY);
|
||||
}
|
||||
for (BlockNode block : mth.getBasicBlocks()) {
|
||||
computeBlockDF(mth, block);
|
||||
@@ -323,7 +330,7 @@ public class BlockMakerVisitor extends AbstractVisitor {
|
||||
}
|
||||
}
|
||||
if (domFrontier == null || domFrontier.cardinality() == 0) {
|
||||
domFrontier = EMPTY_BITSET;
|
||||
domFrontier = EMPTY;
|
||||
}
|
||||
block.setDomFrontier(domFrontier);
|
||||
}
|
||||
@@ -331,7 +338,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);
|
||||
}
|
||||
@@ -357,15 +364,40 @@ public class BlockMakerVisitor extends AbstractVisitor {
|
||||
|
||||
private static void registerLoops(MethodNode mth) {
|
||||
for (BlockNode block : mth.getBasicBlocks()) {
|
||||
List<LoopInfo> loops = block.getAll(AType.LOOP);
|
||||
if (block.contains(AFlag.LOOP_START)) {
|
||||
for (LoopInfo loop : loops) {
|
||||
for (LoopInfo loop : block.getAll(AType.LOOP)) {
|
||||
mth.registerLoop(loop);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void processNestedLoops(MethodNode mth) {
|
||||
if (mth.getLoopsCount() == 0) {
|
||||
return;
|
||||
}
|
||||
for (LoopInfo outLoop : mth.getLoops()) {
|
||||
for (LoopInfo innerLoop : mth.getLoops()) {
|
||||
if (outLoop == innerLoop) {
|
||||
continue;
|
||||
}
|
||||
if (outLoop.getLoopBlocks().containsAll(innerLoop.getLoopBlocks())) {
|
||||
LoopInfo parentLoop = innerLoop.getParentLoop();
|
||||
if (parentLoop != null) {
|
||||
if (parentLoop.getLoopBlocks().containsAll(outLoop.getLoopBlocks())) {
|
||||
outLoop.setParentLoop(parentLoop);
|
||||
innerLoop.setParentLoop(outLoop);
|
||||
} else {
|
||||
parentLoop.setParentLoop(outLoop);
|
||||
}
|
||||
} else {
|
||||
innerLoop.setParentLoop(outLoop);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean modifyBlocksTree(MethodNode mth) {
|
||||
for (BlockNode block : mth.getBasicBlocks()) {
|
||||
if (block.getPredecessors().isEmpty() && block != mth.getEnterBlock()) {
|
||||
@@ -395,11 +427,11 @@ public class BlockMakerVisitor extends AbstractVisitor {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// insert additional blocks if loop has several exits
|
||||
if (loops.size() == 1) {
|
||||
LoopInfo loop = loops.get(0);
|
||||
// insert additional blocks for possible 'break' insertion
|
||||
List<Edge> edges = loop.getExitEdges();
|
||||
if (edges.size() > 1) {
|
||||
if (!edges.isEmpty()) {
|
||||
boolean change = false;
|
||||
for (Edge edge : edges) {
|
||||
BlockNode target = edge.getTarget();
|
||||
@@ -412,12 +444,24 @@ public class BlockMakerVisitor extends AbstractVisitor {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// insert additional blocks for possible 'continue' insertion
|
||||
BlockNode loopEnd = loop.getEnd();
|
||||
if (loopEnd.getPredecessors().size() > 1) {
|
||||
boolean change = false;
|
||||
List<BlockNode> nodes = new ArrayList<BlockNode>(loopEnd.getPredecessors());
|
||||
for (BlockNode pred : nodes) {
|
||||
if (!pred.contains(AFlag.SYNTHETIC)) {
|
||||
insertBlockBetween(mth, pred, loopEnd);
|
||||
change = true;
|
||||
}
|
||||
}
|
||||
if (change) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (splitReturn(mth)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return splitReturn(mth);
|
||||
}
|
||||
|
||||
private static BlockNode insertBlockBetween(MethodNode mth, BlockNode source, BlockNode target) {
|
||||
@@ -439,7 +483,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());
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
package jadx.core.dex.visitors;
|
||||
|
||||
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.ArgType;
|
||||
import jadx.core.dex.instructions.args.NamedArg;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
@@ -43,30 +44,25 @@ public class BlockProcessingHelper {
|
||||
* Set exception handler attribute for whole block
|
||||
*/
|
||||
private static void markExceptionHandlers(BlockNode block) {
|
||||
if (!block.getInstructions().isEmpty()) {
|
||||
InsnNode me = block.getInstructions().get(0);
|
||||
ExcHandlerAttr handlerAttr = me.get(AType.EXC_HANDLER);
|
||||
if (handlerAttr != null && me.getType() == InsnType.MOVE_EXCEPTION) {
|
||||
ExceptionHandler excHandler = handlerAttr.getHandler();
|
||||
assert me.getOffset() == excHandler.getHandleOffset();
|
||||
// set correct type for 'move-exception' operation
|
||||
RegisterArg resArg = me.getResult();
|
||||
NamedArg excArg = (NamedArg) me.getArg(0);
|
||||
ArgType type;
|
||||
if (excHandler.isCatchAll()) {
|
||||
type = ArgType.THROWABLE;
|
||||
excArg.setName("th");
|
||||
} else {
|
||||
type = excHandler.getCatchType().getType();
|
||||
excArg.setName("e");
|
||||
}
|
||||
resArg.forceType(type);
|
||||
excArg.setType(type);
|
||||
|
||||
excHandler.setArg(excArg);
|
||||
block.addAttr(handlerAttr);
|
||||
}
|
||||
if (block.getInstructions().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
InsnNode me = block.getInstructions().get(0);
|
||||
ExcHandlerAttr handlerAttr = me.get(AType.EXC_HANDLER);
|
||||
if (handlerAttr == null || me.getType() != InsnType.MOVE_EXCEPTION) {
|
||||
return;
|
||||
}
|
||||
ExceptionHandler excHandler = handlerAttr.getHandler();
|
||||
block.addAttr(handlerAttr);
|
||||
// set correct type for 'move-exception' operation
|
||||
ArgType type = excHandler.isCatchAll() ? ArgType.THROWABLE : excHandler.getCatchType().getType();
|
||||
|
||||
RegisterArg resArg = me.getResult();
|
||||
resArg = InsnArg.reg(resArg.getRegNum(), type);
|
||||
me.setResult(resArg);
|
||||
me.add(AFlag.DONT_INLINE);
|
||||
|
||||
excHandler.setArg(resArg);
|
||||
}
|
||||
|
||||
private static void processExceptionHandlers(MethodNode mth, BlockNode block) {
|
||||
|
||||
@@ -125,7 +125,7 @@ public class ClassModifier extends AbstractVisitor {
|
||||
List<InsnNode> insns = mth.getBasicBlocks().get(0).getInstructions();
|
||||
if (insns.size() == 1 && insns.get(0).getType() == InsnType.CONSTRUCTOR) {
|
||||
ConstructorInsn constr = (ConstructorInsn) insns.get(0);
|
||||
if (constr.isThis() && mth.getArguments(false).size() >= 1) {
|
||||
if (constr.isThis() && !mth.getArguments(false).isEmpty()) {
|
||||
mth.removeFirstArgument();
|
||||
mth.add(AFlag.DONT_GENERATE);
|
||||
}
|
||||
@@ -167,7 +167,7 @@ public class ClassModifier extends AbstractVisitor {
|
||||
|
||||
private static boolean allBlocksEmpty(List<BlockNode> blocks) {
|
||||
for (BlockNode block : blocks) {
|
||||
if (block.getInstructions().size() != 0) {
|
||||
if (!block.getInstructions().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ 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.EmptyBitSet;
|
||||
import jadx.core.utils.InsnList;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
@@ -55,7 +56,7 @@ public class CodeShrinker extends AbstractVisitor {
|
||||
}
|
||||
|
||||
public static List<RegisterArg> getArgs(InsnNode insn) {
|
||||
LinkedList<RegisterArg> args = new LinkedList<RegisterArg>();
|
||||
List<RegisterArg> args = new LinkedList<RegisterArg>();
|
||||
addArgs(insn, args);
|
||||
return args;
|
||||
}
|
||||
@@ -87,7 +88,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;
|
||||
@@ -95,7 +97,8 @@ public class CodeShrinker extends AbstractVisitor {
|
||||
}
|
||||
|
||||
private boolean canMove(int from, int to) {
|
||||
List<RegisterArg> movedArgs = argsList.get(from).getArgs();
|
||||
ArgsInfo startInfo = argsList.get(from);
|
||||
List<RegisterArg> movedArgs = startInfo.getArgs();
|
||||
int start = from + 1;
|
||||
if (start == to) {
|
||||
// previous instruction or on edge of inline border
|
||||
@@ -104,9 +107,17 @@ public class CodeShrinker extends AbstractVisitor {
|
||||
if (start > to) {
|
||||
throw new JadxRuntimeException("Invalid inline insn positions: " + start + " - " + to);
|
||||
}
|
||||
BitSet args = new BitSet();
|
||||
for (RegisterArg arg : movedArgs) {
|
||||
args.set(arg.getRegNum());
|
||||
BitSet movedSet;
|
||||
if (movedArgs.isEmpty()) {
|
||||
if (startInfo.insn.isConstInsn()) {
|
||||
return true;
|
||||
}
|
||||
movedSet = EmptyBitSet.EMPTY;
|
||||
} else {
|
||||
movedSet = new BitSet();
|
||||
for (RegisterArg arg : movedArgs) {
|
||||
movedSet.set(arg.getRegNum());
|
||||
}
|
||||
}
|
||||
for (int i = start; i < to; i++) {
|
||||
ArgsInfo argsInfo = argsList.get(i);
|
||||
@@ -114,7 +125,7 @@ public class CodeShrinker extends AbstractVisitor {
|
||||
continue;
|
||||
}
|
||||
InsnNode curInsn = argsInfo.insn;
|
||||
if (!curInsn.canReorder() || usedArgAssign(curInsn, args)) {
|
||||
if (!curInsn.canReorder() || usedArgAssign(curInsn, movedSet)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -186,33 +197,35 @@ public class CodeShrinker extends AbstractVisitor {
|
||||
List<WrapInfo> wrapList = new ArrayList<WrapInfo>();
|
||||
for (ArgsInfo argsInfo : argsList) {
|
||||
List<RegisterArg> args = argsInfo.getArgs();
|
||||
for (ListIterator<RegisterArg> it = args.listIterator(args.size()); it.hasPrevious(); ) {
|
||||
if (args.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
ListIterator<RegisterArg> it = args.listIterator(args.size());
|
||||
while (it.hasPrevious()) {
|
||||
RegisterArg arg = it.previous();
|
||||
// if (arg.getName() != null) {
|
||||
// continue;
|
||||
// }
|
||||
SSAVar sVar = arg.getSVar();
|
||||
if (sVar.getAssign() == null || sVar.getVariableUseCount() != 1) {
|
||||
// allow inline only one use arg or 'this'
|
||||
if (sVar.getVariableUseCount() != 1 && !arg.isThis()) {
|
||||
continue;
|
||||
}
|
||||
InsnNode assignInsn = sVar.getAssign().getParentInsn();
|
||||
if (assignInsn == null) {
|
||||
if (assignInsn == null || assignInsn.contains(AFlag.DONT_INLINE)) {
|
||||
continue;
|
||||
}
|
||||
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
|
||||
BlockNode assignBlock = BlockUtils.getBlockByInsn(mth, assignInsn);
|
||||
if (assignBlock != null
|
||||
&& assignInsn != arg.getParentInsn()
|
||||
&& canMoveBetweenBlocks(assignInsn, assignBlock, block, argsInfo.getInsn())) {
|
||||
arg.wrapInstruction(assignInsn);
|
||||
InsnList.remove(assignBlock, assignInsn);
|
||||
@@ -231,7 +244,7 @@ public class CodeShrinker extends AbstractVisitor {
|
||||
}
|
||||
|
||||
private static boolean canMoveBetweenBlocks(InsnNode assignInsn, BlockNode assignBlock,
|
||||
BlockNode useBlock, InsnNode useInsn) {
|
||||
BlockNode useBlock, InsnNode useInsn) {
|
||||
if (!BlockUtils.isPathExists(assignBlock, useBlock)) {
|
||||
return false;
|
||||
}
|
||||
@@ -291,7 +304,7 @@ public class CodeShrinker extends AbstractVisitor {
|
||||
}
|
||||
}
|
||||
// remove method args
|
||||
if (list.size() != 0 && args.size() != 0) {
|
||||
if (!list.isEmpty() && !args.isEmpty()) {
|
||||
list.removeAll(args);
|
||||
}
|
||||
i++;
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
package jadx.core.dex.visitors;
|
||||
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.info.FieldInfo;
|
||||
import jadx.core.dex.instructions.IndexInsnNode;
|
||||
import jadx.core.dex.instructions.InsnType;
|
||||
import jadx.core.dex.instructions.InvokeNode;
|
||||
import jadx.core.dex.instructions.InvokeType;
|
||||
import jadx.core.dex.instructions.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;
|
||||
@@ -24,35 +27,76 @@ 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);
|
||||
if (checkInsn(mth, 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);
|
||||
private static boolean checkInsn(MethodNode mth, InsnNode insn) {
|
||||
if (insn.getType() != InsnType.CONST) {
|
||||
return false;
|
||||
}
|
||||
InsnArg arg = insn.getArg(0);
|
||||
if (!arg.isLiteral()) {
|
||||
return false;
|
||||
}
|
||||
long lit = ((LiteralArg) arg).getLiteral();
|
||||
|
||||
SSAVar sVar = insn.getResult().getSVar();
|
||||
if (lit == 0) {
|
||||
if (checkObjectInline(sVar)) {
|
||||
if (sVar.getUseCount() == 1) {
|
||||
insn.getResult().getAssignInsn().add(AFlag.DONT_INLINE);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
ArgType resType = insn.getResult().getType();
|
||||
// make sure arg has correct type
|
||||
if (!arg.getType().isTypeKnown()) {
|
||||
arg.merge(resType);
|
||||
}
|
||||
return replaceConst(mth, sVar, lit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Don't inline null object if:
|
||||
* - used as instance arg in invoke instruction
|
||||
* - used in 'array.length'
|
||||
*/
|
||||
private static boolean checkObjectInline(SSAVar sVar) {
|
||||
for (RegisterArg useArg : sVar.getUseList()) {
|
||||
InsnNode insn = useArg.getParentInsn();
|
||||
if (insn != null) {
|
||||
InsnType insnType = insn.getType();
|
||||
if (insnType == InsnType.INVOKE) {
|
||||
InvokeNode inv = (InvokeNode) insn;
|
||||
if (inv.getInvokeType() != InvokeType.STATIC
|
||||
&& inv.getArg(0) == useArg) {
|
||||
return true;
|
||||
}
|
||||
} else if (insnType == InsnType.ARRAY_LENGTH) {
|
||||
if (insn.getArg(0) == useArg) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
long lit = ((LiteralArg) arg).getLiteral();
|
||||
return replaceConst(mth, insn, lit);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
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()) {
|
||||
@@ -66,6 +110,10 @@ public class ConstInlinerVisitor extends AbstractVisitor {
|
||||
if (use.size() == 1 || arg.isTypeImmutable()) {
|
||||
// arg used only in one place
|
||||
litArg = InsnArg.lit(literal, arg.getType());
|
||||
} else if (useInsn.getType() == InsnType.MOVE
|
||||
&& !useInsn.getResult().getType().isTypeKnown()) {
|
||||
// save type for 'move' instructions (hard to find type in chains of 'move')
|
||||
litArg = InsnArg.lit(literal, arg.getType());
|
||||
} else {
|
||||
// in most cases type not equal arg.getType()
|
||||
// just set unknown type and run type fixer
|
||||
@@ -80,7 +128,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) {
|
||||
@@ -131,7 +179,7 @@ public class ConstInlinerVisitor extends AbstractVisitor {
|
||||
InvokeNode inv = (InvokeNode) insn;
|
||||
List<ArgType> types = inv.getCallMth().getArgumentsTypes();
|
||||
int count = insn.getArgsCount();
|
||||
int k = (types.size() == count ? 0 : -1);
|
||||
int k = types.size() == count ? 0 : -1;
|
||||
for (int i = 0; i < count; i++) {
|
||||
InsnArg arg = insn.getArg(i);
|
||||
if (!arg.getType().isTypeKnown()) {
|
||||
|
||||
@@ -30,16 +30,28 @@ public class DotGraphVisitor extends AbstractVisitor {
|
||||
private final boolean useRegions;
|
||||
private final boolean rawInsn;
|
||||
|
||||
public DotGraphVisitor(File outDir, boolean useRegions, boolean rawInsn) {
|
||||
public static DotGraphVisitor dump(File outDir) {
|
||||
return new DotGraphVisitor(outDir, false, false);
|
||||
}
|
||||
|
||||
public static DotGraphVisitor dumpRaw(File outDir) {
|
||||
return new DotGraphVisitor(outDir, false, true);
|
||||
}
|
||||
|
||||
public static DotGraphVisitor dumpRegions(File outDir) {
|
||||
return new DotGraphVisitor(outDir, true, false);
|
||||
}
|
||||
|
||||
public static DotGraphVisitor dumpRawRegions(File outDir) {
|
||||
return new DotGraphVisitor(outDir, true, true);
|
||||
}
|
||||
|
||||
private DotGraphVisitor(File outDir, boolean useRegions, boolean rawInsn) {
|
||||
this.dir = outDir;
|
||||
this.useRegions = useRegions;
|
||||
this.rawInsn = rawInsn;
|
||||
}
|
||||
|
||||
public DotGraphVisitor(File outDir, boolean useRegions) {
|
||||
this(outDir, useRegions, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(MethodNode mth) {
|
||||
if (mth.isNoCode()) {
|
||||
|
||||
@@ -18,6 +18,7 @@ import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.utils.ErrorsCounter;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -38,13 +39,12 @@ public class EnumVisitor extends AbstractVisitor {
|
||||
|
||||
// collect enum fields, remove synthetic
|
||||
List<FieldNode> enumFields = new ArrayList<FieldNode>();
|
||||
for (Iterator<FieldNode> it = cls.getFields().iterator(); it.hasNext(); ) {
|
||||
FieldNode f = it.next();
|
||||
for (FieldNode f : cls.getFields()) {
|
||||
if (f.getAccessFlags().isEnum()) {
|
||||
enumFields.add(f);
|
||||
it.remove();
|
||||
f.add(AFlag.DONT_GENERATE);
|
||||
} else if (f.getAccessFlags().isSynthetic()) {
|
||||
it.remove();
|
||||
f.add(AFlag.DONT_GENERATE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,9 +80,8 @@ public class EnumVisitor extends AbstractVisitor {
|
||||
cls.addAttr(attr);
|
||||
|
||||
if (staticMethod == null) {
|
||||
LOG.warn("Enum class init method not found: {}", cls);
|
||||
ErrorsCounter.classError(cls, "Enum class init method not found");
|
||||
// for this broken enum puts found fields and mark as inconsistent
|
||||
cls.add(AFlag.INCONSISTENT_CODE);
|
||||
for (FieldNode field : enumFields) {
|
||||
attr.getFields().add(new EnumField(field.getName(), 0));
|
||||
}
|
||||
@@ -115,21 +114,17 @@ public class EnumVisitor extends AbstractVisitor {
|
||||
for (InsnNode insn : insns) {
|
||||
if (insn.getType() == InsnType.CONSTRUCTOR) {
|
||||
ConstructorInsn co = (ConstructorInsn) insn;
|
||||
|
||||
if (insn.getArgsCount() < 2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ClassInfo clsInfo = co.getClassType();
|
||||
ClassNode constrCls = cls.dex().resolveClass(clsInfo);
|
||||
if (constrCls == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!clsInfo.equals(cls.getClassInfo()) && !constrCls.getAccessFlags().isEnum()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
RegisterArg nameArg = (RegisterArg) insn.getArg(0);
|
||||
// InsnArg pos = insn.getArg(1);
|
||||
// TODO add check: pos == j
|
||||
@@ -137,7 +132,6 @@ public class EnumVisitor extends AbstractVisitor {
|
||||
if (name == null) {
|
||||
throw new JadxException("Unknown enum field name: " + cls);
|
||||
}
|
||||
|
||||
EnumField field = new EnumField(name, insn.getArgsCount() - 2);
|
||||
attr.getFields().add(field);
|
||||
for (int i = 2; i < insn.getArgsCount(); i++) {
|
||||
|
||||
@@ -3,7 +3,6 @@ package jadx.core.dex.visitors;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.nodes.MethodInlineAttr;
|
||||
import jadx.core.dex.info.AccessInfo;
|
||||
import jadx.core.dex.instructions.InsnType;
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
@@ -34,10 +33,8 @@ public class MethodInlineVisitor extends AbstractVisitor {
|
||||
// synthetic field getter
|
||||
BlockNode block = mth.getBasicBlocks().get(1);
|
||||
InsnNode insn = block.getInstructions().get(0);
|
||||
InsnNode inl = new InsnNode(InsnType.ARGS, 1);
|
||||
// set arg from 'return' instruction
|
||||
inl.addArg(insn.getArg(0));
|
||||
addInlineAttr(mth, inl);
|
||||
addInlineAttr(mth, InsnNode.wrapArg(insn.getArg(0)));
|
||||
} else {
|
||||
// synthetic field setter or method invoke
|
||||
if (firstBlock.getInstructions().size() == 1) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package jadx.core.dex.visitors;
|
||||
|
||||
import jadx.core.codegen.TypeGen;
|
||||
import jadx.core.deobf.NameMapper;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
@@ -13,7 +14,9 @@ import jadx.core.dex.instructions.SwitchNode;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.LiteralArg;
|
||||
import jadx.core.dex.instructions.args.NamedArg;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.instructions.args.SSAVar;
|
||||
import jadx.core.dex.instructions.mods.ConstructorInsn;
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
@@ -24,6 +27,7 @@ import jadx.core.dex.trycatch.ExcHandlerAttr;
|
||||
import jadx.core.dex.trycatch.ExceptionHandler;
|
||||
import jadx.core.utils.InstructionRemover;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
@@ -41,8 +45,10 @@ public class ModVisitor extends AbstractVisitor {
|
||||
if (mth.isNoCode()) {
|
||||
return;
|
||||
}
|
||||
removeStep(mth);
|
||||
replaceStep(mth);
|
||||
|
||||
InstructionRemover remover = new InstructionRemover(mth);
|
||||
replaceStep(mth, remover);
|
||||
removeStep(mth, remover);
|
||||
|
||||
checkArgsNames(mth);
|
||||
|
||||
@@ -51,49 +57,16 @@ public class ModVisitor extends AbstractVisitor {
|
||||
}
|
||||
}
|
||||
|
||||
private static void replaceStep(MethodNode mth) {
|
||||
private static void replaceStep(MethodNode mth, InstructionRemover remover) {
|
||||
ClassNode parentClass = mth.getParentClass();
|
||||
for (BlockNode block : mth.getBasicBlocks()) {
|
||||
InstructionRemover remover = new InstructionRemover(mth, block);
|
||||
|
||||
remover.setBlock(block);
|
||||
int size = block.getInstructions().size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
InsnNode insn = block.getInstructions().get(i);
|
||||
switch (insn.getType()) {
|
||||
case INVOKE:
|
||||
InvokeNode inv = (InvokeNode) insn;
|
||||
MethodInfo callMth = inv.getCallMth();
|
||||
if (callMth.isConstructor()) {
|
||||
ConstructorInsn co = new ConstructorInsn(mth, inv);
|
||||
removeInsnForArg(remover, co.getInstanceArg());
|
||||
boolean remove = false;
|
||||
if (co.isSuper() && (co.getArgsCount() == 0 || parentClass.isEnum())) {
|
||||
remove = true;
|
||||
} else if (co.isThis() && co.getArgsCount() == 0) {
|
||||
MethodNode defCo = mth.getParentClass().searchMethodByName(callMth.getShortId());
|
||||
if (defCo == null || defCo.isNoCode()) {
|
||||
// default constructor not implemented
|
||||
remove = true;
|
||||
}
|
||||
}
|
||||
if (remove) {
|
||||
remover.add(insn);
|
||||
} else {
|
||||
replaceInsn(block, i, co);
|
||||
}
|
||||
} else {
|
||||
if (inv.getArgsCount() > 0) {
|
||||
for (int j = 0; j < inv.getArgsCount(); j++) {
|
||||
InsnArg arg = inv.getArg(j);
|
||||
if (arg.isLiteral()) {
|
||||
FieldNode f = parentClass.getConstFieldByLiteralArg((LiteralArg) arg);
|
||||
if (f != null) {
|
||||
arg.wrapInstruction(new IndexInsnNode(InsnType.SGET, f.getFieldInfo(), 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
processInvoke(mth, block, i, remover);
|
||||
break;
|
||||
|
||||
case CONST:
|
||||
@@ -145,17 +118,134 @@ public class ModVisitor extends AbstractVisitor {
|
||||
}
|
||||
}
|
||||
|
||||
private static void processInvoke(MethodNode mth, BlockNode block, int insnNumber, InstructionRemover remover) {
|
||||
ClassNode parentClass = mth.getParentClass();
|
||||
InsnNode insn = block.getInstructions().get(insnNumber);
|
||||
InvokeNode inv = (InvokeNode) insn;
|
||||
MethodInfo callMth = inv.getCallMth();
|
||||
if (callMth.isConstructor()) {
|
||||
InsnNode instArgAssignInsn = ((RegisterArg) inv.getArg(0)).getAssignInsn();
|
||||
ConstructorInsn co = new ConstructorInsn(mth, inv);
|
||||
boolean remove = false;
|
||||
if (co.isSuper() && (co.getArgsCount() == 0 || parentClass.isEnum())) {
|
||||
remove = true;
|
||||
} else if (co.isThis() && co.getArgsCount() == 0) {
|
||||
MethodNode defCo = parentClass.searchMethodByName(callMth.getShortId());
|
||||
if (defCo == null || defCo.isNoCode()) {
|
||||
// default constructor not implemented
|
||||
remove = true;
|
||||
}
|
||||
}
|
||||
// remove super() call in instance initializer
|
||||
if (parentClass.isAnonymous() && mth.isDefaultConstructor() && co.isSuper()) {
|
||||
remove = true;
|
||||
}
|
||||
if (remove) {
|
||||
remover.add(insn);
|
||||
} else {
|
||||
replaceInsn(block, insnNumber, co);
|
||||
if (co.isNewInstance()) {
|
||||
InsnNode newInstInsn = removeAssignChain(instArgAssignInsn, remover, InsnType.NEW_INSTANCE);
|
||||
if (newInstInsn != null) {
|
||||
RegisterArg instArg = newInstInsn.getResult();
|
||||
RegisterArg resultArg = co.getResult();
|
||||
if (!resultArg.equals(instArg)) {
|
||||
// replace all usages of 'instArg' with result of this constructor instruction
|
||||
for (RegisterArg useArg : new ArrayList<RegisterArg>(instArg.getSVar().getUseList())) {
|
||||
RegisterArg dup = resultArg.duplicate();
|
||||
InsnNode parentInsn = useArg.getParentInsn();
|
||||
parentInsn.replaceArg(useArg, dup);
|
||||
dup.setParentInsn(parentInsn);
|
||||
resultArg.getSVar().use(dup);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ConstructorInsn replace = processConstructor(mth, co);
|
||||
if (replace != null) {
|
||||
replaceInsn(block, insnNumber, replace);
|
||||
}
|
||||
}
|
||||
} else if (inv.getArgsCount() > 0) {
|
||||
for (int j = 0; j < inv.getArgsCount(); j++) {
|
||||
InsnArg arg = inv.getArg(j);
|
||||
if (arg.isLiteral()) {
|
||||
FieldNode f = parentClass.getConstFieldByLiteralArg((LiteralArg) arg);
|
||||
if (f != null) {
|
||||
arg.wrapInstruction(new IndexInsnNode(InsnType.SGET, f.getFieldInfo(), 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace call of synthetic constructor
|
||||
*/
|
||||
private static ConstructorInsn processConstructor(MethodNode mth, ConstructorInsn co) {
|
||||
MethodNode callMth = mth.dex().resolveMethod(co.getCallMth());
|
||||
if (callMth == null
|
||||
|| !callMth.getAccessFlags().isSynthetic()
|
||||
|| !allArgsNull(co)) {
|
||||
return null;
|
||||
}
|
||||
ClassNode classNode = mth.dex().resolveClass(callMth.getParentClass().getClassInfo());
|
||||
if (classNode == null) {
|
||||
return null;
|
||||
}
|
||||
boolean passThis = co.getArgsCount() >= 1 && co.getArg(0).isThis();
|
||||
String ctrId = "<init>(" + (passThis ? TypeGen.signature(co.getArg(0).getType()) : "") + ")V";
|
||||
MethodNode defCtr = classNode.searchMethodByName(ctrId);
|
||||
if (defCtr == null) {
|
||||
return null;
|
||||
}
|
||||
ConstructorInsn newInsn = new ConstructorInsn(defCtr.getMethodInfo(), co.getCallType(), co.getInstanceArg());
|
||||
newInsn.setResult(co.getResult());
|
||||
return newInsn;
|
||||
}
|
||||
|
||||
private static boolean allArgsNull(InsnNode insn) {
|
||||
for (InsnArg insnArg : insn.getArguments()) {
|
||||
if (insnArg.isLiteral()) {
|
||||
LiteralArg lit = (LiteralArg) insnArg;
|
||||
if (lit.getLiteral() != 0) {
|
||||
return false;
|
||||
}
|
||||
} else if (!insnArg.isThis()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove instructions on 'move' chain until instruction with type 'insnType'
|
||||
*/
|
||||
private static InsnNode removeAssignChain(InsnNode insn, InstructionRemover remover, InsnType insnType) {
|
||||
if (insn == null) {
|
||||
return null;
|
||||
}
|
||||
remover.add(insn);
|
||||
InsnType type = insn.getType();
|
||||
if (type == insnType) {
|
||||
return insn;
|
||||
}
|
||||
if (type == InsnType.MOVE) {
|
||||
RegisterArg arg = (RegisterArg) insn.getArg(0);
|
||||
return removeAssignChain(arg.getAssignInsn(), remover, insnType);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove unnecessary instructions
|
||||
*/
|
||||
private static void removeStep(MethodNode mth) {
|
||||
private static void removeStep(MethodNode mth, InstructionRemover remover) {
|
||||
for (BlockNode block : mth.getBasicBlocks()) {
|
||||
InstructionRemover remover = new InstructionRemover(mth, block);
|
||||
|
||||
remover.setBlock(block);
|
||||
int size = block.getInstructions().size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
InsnNode insn = block.getInstructions().get(i);
|
||||
|
||||
switch (insn.getType()) {
|
||||
case NOP:
|
||||
case GOTO:
|
||||
@@ -201,6 +291,7 @@ public class ModVisitor extends AbstractVisitor {
|
||||
}
|
||||
ExceptionHandler excHandler = handlerAttr.getHandler();
|
||||
boolean noExitNode = true; // check if handler has exit edge to block not from this handler
|
||||
boolean reThrow = false;
|
||||
for (BlockNode excBlock : excHandler.getBlocks()) {
|
||||
if (noExitNode) {
|
||||
noExitNode = excHandler.getBlocks().containsAll(excBlock.getCleanSuccessors());
|
||||
@@ -211,11 +302,11 @@ public class ModVisitor extends AbstractVisitor {
|
||||
if (excHandler.isCatchAll()
|
||||
&& size > 0
|
||||
&& insns.get(size - 1).getType() == InsnType.THROW) {
|
||||
|
||||
reThrow = true;
|
||||
InstructionRemover.remove(mth, excBlock, size - 1);
|
||||
|
||||
// move not removed instructions to 'finally' block
|
||||
if (insns.size() != 0) {
|
||||
if (!insns.isEmpty()) {
|
||||
// TODO: support instructions from several blocks
|
||||
// tryBlock.setFinalBlockFromInsns(mth, insns);
|
||||
// TODO: because of incomplete realization don't extract final block,
|
||||
@@ -226,19 +317,36 @@ public class ModVisitor extends AbstractVisitor {
|
||||
}
|
||||
|
||||
List<InsnNode> blockInsns = block.getInstructions();
|
||||
if (blockInsns.size() > 0) {
|
||||
if (!blockInsns.isEmpty()) {
|
||||
InsnNode insn = blockInsns.get(0);
|
||||
if (insn.getType() == InsnType.MOVE_EXCEPTION
|
||||
&& insn.getResult().getSVar().getUseCount() == 0) {
|
||||
InstructionRemover.remove(mth, block, 0);
|
||||
if (insn.getType() == InsnType.MOVE_EXCEPTION) {
|
||||
// result arg used both in this insn and exception handler,
|
||||
RegisterArg resArg = insn.getResult();
|
||||
ArgType type = excHandler.isCatchAll() ? ArgType.THROWABLE : excHandler.getCatchType().getType();
|
||||
String name = excHandler.isCatchAll() ? "th" : "e";
|
||||
if (resArg.getName() == null) {
|
||||
resArg.setName(name);
|
||||
}
|
||||
SSAVar sVar = insn.getResult().getSVar();
|
||||
if (sVar.getUseCount() == 0) {
|
||||
excHandler.setArg(new NamedArg(name, type));
|
||||
InstructionRemover.remove(mth, block, 0);
|
||||
} else if (sVar.isUsedInPhi()) {
|
||||
// exception var moved to external variable => replace with 'move' insn
|
||||
InsnNode moveInsn = new InsnNode(InsnType.MOVE, 1);
|
||||
moveInsn.setResult(insn.getResult());
|
||||
NamedArg namedArg = new NamedArg(name, type);
|
||||
moveInsn.addArg(namedArg);
|
||||
excHandler.setArg(namedArg);
|
||||
replaceInsn(block, 0, moveInsn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int totalSize = 0;
|
||||
for (BlockNode excBlock : excHandler.getBlocks()) {
|
||||
totalSize += excBlock.getInstructions().size();
|
||||
}
|
||||
if (totalSize == 0 && noExitNode) {
|
||||
if (totalSize == 0 && noExitNode && reThrow) {
|
||||
handlerAttr.getTryBlock().removeHandler(mth, excHandler);
|
||||
}
|
||||
}
|
||||
@@ -253,16 +361,6 @@ public class ModVisitor extends AbstractVisitor {
|
||||
block.getInstructions().set(i, insn);
|
||||
}
|
||||
|
||||
/**
|
||||
* In argument not used in other instructions then remove assign instruction.
|
||||
*/
|
||||
private static void removeInsnForArg(InstructionRemover remover, RegisterArg arg) {
|
||||
if (arg.getSVar().getUseCount() == 0
|
||||
&& arg.getAssignInsn() != null) {
|
||||
remover.add(arg.getAssignInsn());
|
||||
}
|
||||
}
|
||||
|
||||
private static void checkArgsNames(MethodNode mth) {
|
||||
for (RegisterArg arg : mth.getArguments(false)) {
|
||||
String name = arg.getName();
|
||||
|
||||
@@ -32,7 +32,7 @@ public class PrepareForCodeGen extends AbstractVisitor {
|
||||
for (BlockNode block : blocks) {
|
||||
removeInstructions(block);
|
||||
checkInline(block);
|
||||
removeParenthesis(block);
|
||||
// removeParenthesis(block);
|
||||
modifyArith(block);
|
||||
}
|
||||
}
|
||||
@@ -45,6 +45,7 @@ public class PrepareForCodeGen extends AbstractVisitor {
|
||||
case NOP:
|
||||
case MONITOR_ENTER:
|
||||
case MONITOR_EXIT:
|
||||
case MOVE_EXCEPTION:
|
||||
it.remove();
|
||||
break;
|
||||
|
||||
@@ -123,25 +124,20 @@ public class PrepareForCodeGen extends AbstractVisitor {
|
||||
*/
|
||||
private static void modifyArith(BlockNode block) {
|
||||
List<InsnNode> list = block.getInstructions();
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
InsnNode insn = list.get(i);
|
||||
if (insn.getType() != InsnType.ARITH) {
|
||||
continue;
|
||||
}
|
||||
ArithNode arith = (ArithNode) insn;
|
||||
RegisterArg res = arith.getResult();
|
||||
InsnArg arg = arith.getArg(0);
|
||||
boolean replace = false;
|
||||
|
||||
if (res.equals(arg)) {
|
||||
replace = true;
|
||||
} else if (arg.isRegister()) {
|
||||
RegisterArg regArg = (RegisterArg) arg;
|
||||
replace = res.equalRegisterAndType(regArg);
|
||||
}
|
||||
if (replace) {
|
||||
ArithNode newArith = new ArithNode(arith.getOp(), res, arith.getArg(1));
|
||||
list.set(i, newArith);
|
||||
for (InsnNode insn : list) {
|
||||
if (insn.getType() == InsnType.ARITH) {
|
||||
RegisterArg res = insn.getResult();
|
||||
InsnArg arg = insn.getArg(0);
|
||||
boolean replace = false;
|
||||
if (res.equals(arg)) {
|
||||
replace = true;
|
||||
} else if (arg.isRegister()) {
|
||||
RegisterArg regArg = (RegisterArg) arg;
|
||||
replace = res.equalRegisterAndType(regArg);
|
||||
}
|
||||
if (replace) {
|
||||
insn.add(AFlag.ARITH_ONEARG);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,259 @@
|
||||
package jadx.core.dex.visitors;
|
||||
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.nodes.EnumMapAttr;
|
||||
import jadx.core.dex.info.AccessInfo;
|
||||
import jadx.core.dex.info.FieldInfo;
|
||||
import jadx.core.dex.instructions.IndexInsnNode;
|
||||
import jadx.core.dex.instructions.InsnType;
|
||||
import jadx.core.dex.instructions.InvokeNode;
|
||||
import jadx.core.dex.instructions.SwitchNode;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.InsnWrapArg;
|
||||
import jadx.core.dex.instructions.args.LiteralArg;
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.utils.InstructionRemover;
|
||||
import jadx.core.utils.exceptions.DecodeException;
|
||||
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);
|
||||
|
||||
case SWITCH:
|
||||
return processEnumSwitch(mth, (SwitchNode) insn);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
private static InsnNode processEnumSwitch(MethodNode mth, SwitchNode insn) {
|
||||
InsnArg arg = insn.getArg(0);
|
||||
if (!arg.isInsnWrap()) {
|
||||
return null;
|
||||
}
|
||||
InsnNode wrapInsn = ((InsnWrapArg) arg).getWrapInsn();
|
||||
if (wrapInsn.getType() != InsnType.AGET) {
|
||||
return null;
|
||||
}
|
||||
EnumMapInfo enumMapInfo = checkEnumMapAccess(mth, wrapInsn);
|
||||
if (enumMapInfo == null) {
|
||||
return null;
|
||||
}
|
||||
FieldNode enumMapField = enumMapInfo.getMapField();
|
||||
InsnArg invArg = enumMapInfo.getArg();
|
||||
|
||||
EnumMapAttr.KeyValueMap valueMap = getEnumMap(mth, enumMapField);
|
||||
if (valueMap == null) {
|
||||
return null;
|
||||
}
|
||||
Object[] keys = insn.getKeys();
|
||||
for (Object key : keys) {
|
||||
Object newKey = valueMap.get(key);
|
||||
if (newKey == null) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
// replace confirmed
|
||||
if (!insn.replaceArg(arg, invArg)) {
|
||||
return null;
|
||||
}
|
||||
for (int i = 0; i < keys.length; i++) {
|
||||
keys[i] = valueMap.get(keys[i]);
|
||||
}
|
||||
enumMapField.add(AFlag.DONT_GENERATE);
|
||||
checkAndHideClass(enumMapField.getParentClass());
|
||||
return null;
|
||||
}
|
||||
|
||||
private static EnumMapAttr.KeyValueMap getEnumMap(MethodNode mth, FieldNode field) {
|
||||
ClassNode syntheticClass = field.getParentClass();
|
||||
EnumMapAttr mapAttr = syntheticClass.get(AType.ENUM_MAP);
|
||||
if (mapAttr != null) {
|
||||
return mapAttr.getMap(field);
|
||||
}
|
||||
mapAttr = new EnumMapAttr();
|
||||
syntheticClass.addAttr(mapAttr);
|
||||
|
||||
MethodNode clsInitMth = syntheticClass.searchMethodByName("<clinit>()V");
|
||||
if (clsInitMth == null || clsInitMth.isNoCode()) {
|
||||
return null;
|
||||
}
|
||||
if (clsInitMth.getBasicBlocks() == null) {
|
||||
try {
|
||||
clsInitMth.load();
|
||||
} catch (DecodeException e) {
|
||||
LOG.error("Load failed", e);
|
||||
return null;
|
||||
}
|
||||
if (clsInitMth.getBasicBlocks() == null) {
|
||||
// TODO:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
for (BlockNode block : clsInitMth.getBasicBlocks()) {
|
||||
for (InsnNode insn : block.getInstructions()) {
|
||||
if (insn.getType() == InsnType.APUT) {
|
||||
addToEnumMap(mth, mapAttr, insn);
|
||||
}
|
||||
}
|
||||
}
|
||||
return mapAttr.getMap(field);
|
||||
}
|
||||
|
||||
private static void addToEnumMap(MethodNode mth, EnumMapAttr mapAttr, InsnNode aputInsn) {
|
||||
InsnArg litArg = aputInsn.getArg(2);
|
||||
if (!litArg.isLiteral()) {
|
||||
return;
|
||||
}
|
||||
EnumMapInfo mapInfo = checkEnumMapAccess(mth, aputInsn);
|
||||
if (mapInfo == null) {
|
||||
return;
|
||||
}
|
||||
InsnArg enumArg = mapInfo.getArg();
|
||||
FieldNode field = mapInfo.getMapField();
|
||||
if (field == null || !enumArg.isInsnWrap()) {
|
||||
return;
|
||||
}
|
||||
InsnNode sget = ((InsnWrapArg) enumArg).getWrapInsn();
|
||||
if (!(sget instanceof IndexInsnNode)) {
|
||||
return;
|
||||
}
|
||||
Object index = ((IndexInsnNode) sget).getIndex();
|
||||
if (!(index instanceof FieldInfo)) {
|
||||
return;
|
||||
}
|
||||
FieldNode fieldNode = mth.dex().resolveField((FieldInfo) index);
|
||||
if (fieldNode == null) {
|
||||
return;
|
||||
}
|
||||
int literal = (int) ((LiteralArg) litArg).getLiteral();
|
||||
mapAttr.add(field, literal, fieldNode);
|
||||
}
|
||||
|
||||
public static EnumMapInfo checkEnumMapAccess(MethodNode mth, InsnNode checkInsn) {
|
||||
InsnArg sgetArg = checkInsn.getArg(0);
|
||||
InsnArg invArg = checkInsn.getArg(1);
|
||||
if (!sgetArg.isInsnWrap() || !invArg.isInsnWrap()) {
|
||||
return null;
|
||||
}
|
||||
InsnNode invInsn = ((InsnWrapArg) invArg).getWrapInsn();
|
||||
InsnNode sgetInsn = ((InsnWrapArg) sgetArg).getWrapInsn();
|
||||
if (invInsn.getType() != InsnType.INVOKE || sgetInsn.getType() != InsnType.SGET) {
|
||||
return null;
|
||||
}
|
||||
InvokeNode inv = (InvokeNode) invInsn;
|
||||
if (!inv.getCallMth().getShortId().equals("ordinal()I")) {
|
||||
return null;
|
||||
}
|
||||
ClassNode enumCls = mth.dex().resolveClass(inv.getCallMth().getDeclClass());
|
||||
if (enumCls == null || !enumCls.isEnum()) {
|
||||
return null;
|
||||
}
|
||||
Object index = ((IndexInsnNode) sgetInsn).getIndex();
|
||||
if (!(index instanceof FieldInfo)) {
|
||||
return null;
|
||||
}
|
||||
FieldNode enumMapField = mth.dex().resolveField((FieldInfo) index);
|
||||
if (enumMapField == null || !enumMapField.getAccessFlags().isSynthetic()) {
|
||||
return null;
|
||||
}
|
||||
return new EnumMapInfo(inv.getArg(0), enumMapField);
|
||||
}
|
||||
|
||||
/**
|
||||
* If all static final synthetic fields have DONT_GENERATE => hide whole class
|
||||
*/
|
||||
private static void checkAndHideClass(ClassNode cls) {
|
||||
for (FieldNode field : cls.getFields()) {
|
||||
AccessInfo af = field.getAccessFlags();
|
||||
if (af.isSynthetic() && af.isStatic() && af.isFinal()
|
||||
&& !field.contains(AFlag.DONT_GENERATE)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
cls.add(AFlag.DONT_GENERATE);
|
||||
}
|
||||
|
||||
private static class EnumMapInfo {
|
||||
private final InsnArg arg;
|
||||
private final FieldNode mapField;
|
||||
|
||||
public EnumMapInfo(InsnArg arg, FieldNode mapField) {
|
||||
this.arg = arg;
|
||||
this.mapField = mapField;
|
||||
}
|
||||
|
||||
public InsnArg getArg() {
|
||||
return arg;
|
||||
}
|
||||
|
||||
public FieldNode getMapField() {
|
||||
return mapField;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,9 +15,11 @@ import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.InsnWrapArg;
|
||||
import jadx.core.dex.instructions.args.LiteralArg;
|
||||
import jadx.core.dex.instructions.mods.ConstructorInsn;
|
||||
import jadx.core.dex.instructions.mods.TernaryInsn;
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.regions.conditions.IfCondition;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
@@ -62,6 +64,9 @@ public class SimplifyVisitor extends AbstractVisitor {
|
||||
case IF:
|
||||
simplifyIf((IfNode) insn);
|
||||
break;
|
||||
case TERNARY:
|
||||
simplifyTernary((TernaryInsn) insn);
|
||||
break;
|
||||
|
||||
case INVOKE:
|
||||
return convertInvoke(mth, insn);
|
||||
@@ -99,12 +104,24 @@ public class SimplifyVisitor extends AbstractVisitor {
|
||||
&& ((LiteralArg) insn.getArg(1)).getLiteral() == 0) {
|
||||
insn.changeCondition(insn.getOp(), wi.getArg(0), wi.getArg(1));
|
||||
} else {
|
||||
LOG.warn("TODO: cmp" + insn);
|
||||
LOG.warn("TODO: cmp {}", insn);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simplify condition in ternary operation
|
||||
*/
|
||||
private static void simplifyTernary(TernaryInsn insn) {
|
||||
IfCondition condition = insn.getCondition();
|
||||
if (condition.isCompare()) {
|
||||
simplifyIf(condition.getCompare().getInsn());
|
||||
} else {
|
||||
insn.simplifyCondition();
|
||||
}
|
||||
}
|
||||
|
||||
private static InsnNode convertInvoke(MethodNode mth, InsnNode insn) {
|
||||
MethodInfo callMth = ((InvokeNode) insn).getCallMth();
|
||||
if (callMth.getDeclClass().getFullName().equals(Consts.CLASS_STRING_BUILDER)
|
||||
|
||||
@@ -6,12 +6,14 @@ import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.dex.nodes.IBlock;
|
||||
import jadx.core.dex.nodes.IRegion;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.regions.LoopRegion;
|
||||
import jadx.core.dex.regions.loops.LoopRegion;
|
||||
import jadx.core.dex.visitors.AbstractVisitor;
|
||||
import jadx.core.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,29 @@ 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,335 @@
|
||||
package jadx.core.dex.visitors.regions;
|
||||
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.nodes.LoopInfo;
|
||||
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.conditions.IfCondition;
|
||||
import jadx.core.dex.regions.conditions.IfCondition.Mode;
|
||||
import jadx.core.dex.regions.conditions.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.getNextBlock;
|
||||
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 searchNestedIf(IfInfo info) {
|
||||
IfInfo tmp = mergeNestedIfNodes(info);
|
||||
return tmp != null ? tmp : 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 = isBadBranchBlock(info, thenBlock);
|
||||
boolean badElse = isBadBranchBlock(info, elseBlock);
|
||||
if (badThen && badElse) {
|
||||
LOG.debug("Stop processing blocks after 'if': {}, method: {}", info.getIfBlock(), mth);
|
||||
return null;
|
||||
}
|
||||
if (badElse) {
|
||||
info = new IfInfo(info, thenBlock, null);
|
||||
info.setOutBlock(elseBlock);
|
||||
} else if (badThen) {
|
||||
info = IfInfo.invert(info);
|
||||
info = new IfInfo(info, 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 isBadBranchBlock(IfInfo info, BlockNode block) {
|
||||
// check if block at end of loop edge
|
||||
if (block.contains(AFlag.LOOP_START) && block.getPredecessors().size() == 1) {
|
||||
BlockNode pred = block.getPredecessors().get(0);
|
||||
if (pred.contains(AFlag.LOOP_END)) {
|
||||
List<LoopInfo> startLoops = block.getAll(AType.LOOP);
|
||||
List<LoopInfo> endLoops = pred.getAll(AType.LOOP);
|
||||
// search for same loop
|
||||
for (LoopInfo startLoop : startLoops) {
|
||||
for (LoopInfo endLoop : endLoops) {
|
||||
if (startLoop == endLoop) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return !allPathsFromIf(block, info);
|
||||
}
|
||||
|
||||
private static boolean allPathsFromIf(BlockNode block, IfInfo info) {
|
||||
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 checkForTernaryInCondition(currentIf);
|
||||
}
|
||||
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);
|
||||
}
|
||||
} else {
|
||||
return currentIf;
|
||||
}
|
||||
}
|
||||
|
||||
IfInfo result = mergeIfInfo(currentIf, nextIf, followThenBranch);
|
||||
// search next nested if block
|
||||
return searchNestedIf(result);
|
||||
}
|
||||
|
||||
private static IfInfo checkForTernaryInCondition(IfInfo currentIf) {
|
||||
IfInfo nextThen = getNextIf(currentIf, currentIf.getThenBlock());
|
||||
IfInfo nextElse = getNextIf(currentIf, currentIf.getElseBlock());
|
||||
if (nextThen == null || nextElse == null) {
|
||||
return null;
|
||||
}
|
||||
if (!nextThen.getIfBlock().getDomFrontier().equals(nextElse.getIfBlock().getDomFrontier())) {
|
||||
return null;
|
||||
}
|
||||
nextThen = searchNestedIf(nextThen);
|
||||
nextElse = searchNestedIf(nextElse);
|
||||
if (nextThen.getThenBlock() == nextElse.getThenBlock()
|
||||
&& nextThen.getElseBlock() == nextElse.getElseBlock()) {
|
||||
return mergeTernaryConditions(currentIf, nextThen, nextElse);
|
||||
}
|
||||
if (nextThen.getThenBlock() == nextElse.getElseBlock()
|
||||
&& nextThen.getElseBlock() == nextElse.getThenBlock()) {
|
||||
nextElse = IfInfo.invert(nextElse);
|
||||
return mergeTernaryConditions(currentIf, nextThen, nextElse);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static IfInfo mergeTernaryConditions(IfInfo currentIf, IfInfo nextThen, IfInfo nextElse) {
|
||||
IfCondition newCondition = IfCondition.ternary(currentIf.getCondition(),
|
||||
nextThen.getCondition(), nextElse.getCondition());
|
||||
IfInfo result = new IfInfo(newCondition, nextThen.getThenBlock(), nextThen.getElseBlock());
|
||||
result.setIfBlock(currentIf.getIfBlock());
|
||||
result.merge(currentIf, nextThen, nextElse);
|
||||
confirmMerge(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
IfCondition condition = IfCondition.merge(mergeOperation, first.getCondition(), second.getCondition());
|
||||
IfInfo result = new IfInfo(condition, second);
|
||||
result.setIfBlock(first.getIfBlock());
|
||||
result.merge(first, second);
|
||||
|
||||
BlockNode otherPathBlock = followThenBranch ? first.getElseBlock() : first.getThenBlock();
|
||||
skipSimplePath(otherPathBlock, result.getSkipBlocks());
|
||||
return result;
|
||||
}
|
||||
|
||||
static void confirmMerge(IfInfo info) {
|
||||
if (info.getMergedBlocks().size() > 1) {
|
||||
for (BlockNode block : info.getMergedBlocks()) {
|
||||
if (block != info.getIfBlock()) {
|
||||
block.add(AFlag.SKIP);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!info.getSkipBlocks().isEmpty()) {
|
||||
for (BlockNode block : info.getSkipBlocks()) {
|
||||
block.add(AFlag.SKIP);
|
||||
}
|
||||
info.getSkipBlocks().clear();
|
||||
}
|
||||
}
|
||||
|
||||
private static IfInfo getNextIf(IfInfo info, BlockNode block) {
|
||||
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;
|
||||
}
|
||||
|
||||
private static void skipSimplePath(BlockNode block, Set<BlockNode> skipped) {
|
||||
while (block != null
|
||||
&& block.getCleanSuccessors().size() < 2
|
||||
&& block.getPredecessors().size() == 1) {
|
||||
skipped.add(block);
|
||||
block = getNextBlock(block);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,18 +6,31 @@ import jadx.core.dex.nodes.IBlock;
|
||||
import jadx.core.dex.nodes.IContainer;
|
||||
import jadx.core.dex.nodes.IRegion;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.regions.IfCondition;
|
||||
import jadx.core.dex.regions.IfRegion;
|
||||
import jadx.core.dex.regions.Region;
|
||||
import jadx.core.dex.regions.conditions.IfCondition;
|
||||
import jadx.core.dex.regions.conditions.IfCondition.Mode;
|
||||
import jadx.core.dex.regions.conditions.IfRegion;
|
||||
import jadx.core.dex.visitors.AbstractVisitor;
|
||||
import jadx.core.utils.RegionUtils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static jadx.core.utils.RegionUtils.insnsCount;
|
||||
|
||||
public class IfRegionVisitor extends AbstractVisitor implements IRegionVisitor, IRegionIterativeVisitor {
|
||||
|
||||
@Override
|
||||
public void visit(MethodNode mth) {
|
||||
// collapse ternary operators
|
||||
DepthRegionTraversal.traverseAllIterative(mth, new IRegionIterativeVisitor() {
|
||||
@Override
|
||||
public boolean visitRegion(MethodNode mth, IRegion region) {
|
||||
if (region instanceof IfRegion) {
|
||||
return TernaryMod.makeTernaryInsn(mth, (IfRegion) region);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
DepthRegionTraversal.traverseAll(mth, this);
|
||||
DepthRegionTraversal.traverseAllIterative(mth, this);
|
||||
}
|
||||
@@ -32,7 +45,7 @@ public class IfRegionVisitor extends AbstractVisitor implements IRegionVisitor,
|
||||
@Override
|
||||
public boolean visitRegion(MethodNode mth, IRegion region) {
|
||||
if (region instanceof IfRegion) {
|
||||
return removeRedundantElseBlock((IfRegion) region);
|
||||
return removeRedundantElseBlock(mth, (IfRegion) region);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -48,22 +61,47 @@ 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);
|
||||
}
|
||||
|
||||
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 +112,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
|
||||
*/
|
||||
@@ -91,23 +136,40 @@ public class IfRegionVisitor extends AbstractVisitor implements IRegionVisitor,
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean removeRedundantElseBlock(IfRegion ifRegion) {
|
||||
if (ifRegion.getElseRegion() != null
|
||||
&& !ifRegion.contains(AFlag.ELSE_IF_CHAIN)
|
||||
&& !ifRegion.getElseRegion().contains(AFlag.ELSE_IF_CHAIN)
|
||||
&& RegionUtils.hasExitBlock(ifRegion.getThenRegion())) {
|
||||
IRegion parent = ifRegion.getParent();
|
||||
Region newRegion = new Region(parent);
|
||||
if (parent.replaceSubBlock(ifRegion, newRegion)) {
|
||||
newRegion.add(ifRegion);
|
||||
newRegion.add(ifRegion.getElseRegion());
|
||||
ifRegion.setElseRegion(null);
|
||||
return true;
|
||||
}
|
||||
private static boolean removeRedundantElseBlock(MethodNode mth, IfRegion ifRegion) {
|
||||
if (ifRegion.getElseRegion() == null
|
||||
|| ifRegion.contains(AFlag.ELSE_IF_CHAIN)
|
||||
|| ifRegion.getElseRegion().contains(AFlag.ELSE_IF_CHAIN)) {
|
||||
return false;
|
||||
}
|
||||
if (!hasBranchTerminator(ifRegion.getThenRegion())) {
|
||||
return false;
|
||||
}
|
||||
// code style check:
|
||||
// will remove 'return;' from 'then' and 'else' with one instruction
|
||||
// see #jadx.tests.integration.conditions.TestConditions9
|
||||
if (mth.getReturnType() == ArgType.VOID
|
||||
&& insnsCount(ifRegion.getThenRegion()) == 2
|
||||
&& insnsCount(ifRegion.getElseRegion()) == 2) {
|
||||
return false;
|
||||
}
|
||||
IRegion parent = ifRegion.getParent();
|
||||
Region newRegion = new Region(parent);
|
||||
if (parent.replaceSubBlock(ifRegion, newRegion)) {
|
||||
newRegion.add(ifRegion);
|
||||
newRegion.add(ifRegion.getElseRegion());
|
||||
ifRegion.setElseRegion(null);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean hasBranchTerminator(IContainer region) {
|
||||
// TODO: check for exception throw
|
||||
return RegionUtils.hasExitBlock(region)
|
||||
|| RegionUtils.hasBreakInsn(region);
|
||||
}
|
||||
|
||||
private static void invertIfRegion(IfRegion ifRegion) {
|
||||
IContainer elseRegion = ifRegion.getElseRegion();
|
||||
if (elseRegion != null) {
|
||||
|
||||
@@ -0,0 +1,356 @@
|
||||
package jadx.core.dex.visitors.regions;
|
||||
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.ArithNode;
|
||||
import jadx.core.dex.instructions.ArithOp;
|
||||
import jadx.core.dex.instructions.IfOp;
|
||||
import jadx.core.dex.instructions.InsnType;
|
||||
import jadx.core.dex.instructions.InvokeNode;
|
||||
import jadx.core.dex.instructions.InvokeType;
|
||||
import jadx.core.dex.instructions.PhiInsn;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.InsnWrapArg;
|
||||
import jadx.core.dex.instructions.args.LiteralArg;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.instructions.args.SSAVar;
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.dex.nodes.IBlock;
|
||||
import jadx.core.dex.nodes.IRegion;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.regions.conditions.Compare;
|
||||
import jadx.core.dex.regions.conditions.IfCondition;
|
||||
import jadx.core.dex.regions.loops.ForEachLoop;
|
||||
import jadx.core.dex.regions.loops.ForLoop;
|
||||
import jadx.core.dex.regions.loops.LoopRegion;
|
||||
import jadx.core.dex.regions.loops.LoopType;
|
||||
import jadx.core.dex.visitors.AbstractVisitor;
|
||||
import jadx.core.dex.visitors.CodeShrinker;
|
||||
import jadx.core.utils.BlockUtils;
|
||||
import jadx.core.utils.InstructionRemover;
|
||||
import jadx.core.utils.RegionUtils;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class LoopRegionVisitor extends AbstractVisitor implements IRegionVisitor {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(LoopRegionVisitor.class);
|
||||
|
||||
@Override
|
||||
public void visit(MethodNode mth) {
|
||||
DepthRegionTraversal.traverseAll(mth, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enterRegion(MethodNode mth, IRegion region) {
|
||||
if (region instanceof LoopRegion) {
|
||||
processLoopRegion(mth, (LoopRegion) region);
|
||||
}
|
||||
}
|
||||
|
||||
private static void processLoopRegion(MethodNode mth, LoopRegion loopRegion) {
|
||||
if (loopRegion.isConditionAtEnd()) {
|
||||
return;
|
||||
}
|
||||
IfCondition condition = loopRegion.getCondition();
|
||||
if (condition == null) {
|
||||
return;
|
||||
}
|
||||
if (checkForIndexedLoop(mth, loopRegion, condition)) {
|
||||
return;
|
||||
}
|
||||
if (checkIterableForEach(mth, loopRegion, condition)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for indexed loop.
|
||||
*/
|
||||
private static boolean checkForIndexedLoop(MethodNode mth, LoopRegion loopRegion, IfCondition condition) {
|
||||
InsnNode incrInsn = RegionUtils.getLastInsn(loopRegion);
|
||||
if (incrInsn == null) {
|
||||
return false;
|
||||
}
|
||||
RegisterArg incrArg = incrInsn.getResult();
|
||||
if (incrArg == null
|
||||
|| incrArg.getSVar() == null
|
||||
|| !incrArg.getSVar().isUsedInPhi()) {
|
||||
return false;
|
||||
}
|
||||
PhiInsn phiInsn = incrArg.getSVar().getUsedInPhi();
|
||||
if (phiInsn == null
|
||||
|| phiInsn.getArgsCount() != 2
|
||||
|| !phiInsn.getArg(1).equals(incrArg)
|
||||
|| incrArg.getSVar().getUseCount() != 1) {
|
||||
return false;
|
||||
}
|
||||
RegisterArg arg = phiInsn.getResult();
|
||||
List<RegisterArg> condArgs = condition.getRegisterArgs();
|
||||
if (!condArgs.contains(arg) || arg.getSVar().isUsedInPhi()) {
|
||||
return false;
|
||||
}
|
||||
RegisterArg initArg = phiInsn.getArg(0);
|
||||
InsnNode initInsn = initArg.getAssignInsn();
|
||||
if (initInsn == null || initArg.getSVar().getUseCount() != 1) {
|
||||
return false;
|
||||
}
|
||||
if (!usedOnlyInLoop(mth, loopRegion, arg)) {
|
||||
return false;
|
||||
}
|
||||
// can't make loop if argument from increment instruction is assign in loop
|
||||
List<RegisterArg> args = new LinkedList<RegisterArg>();
|
||||
incrInsn.getRegisterArgs(args);
|
||||
for (RegisterArg iArg : args) {
|
||||
if (assignOnlyInLoop(mth, loopRegion, iArg)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// all checks passed
|
||||
initInsn.add(AFlag.SKIP);
|
||||
incrInsn.add(AFlag.SKIP);
|
||||
LoopType arrForEach = checkArrayForEach(mth, initInsn, incrInsn, condition);
|
||||
if (arrForEach != null) {
|
||||
loopRegion.setType(arrForEach);
|
||||
return true;
|
||||
}
|
||||
loopRegion.setType(new ForLoop(initInsn, incrInsn));
|
||||
return true;
|
||||
}
|
||||
|
||||
private static LoopType checkArrayForEach(MethodNode mth, InsnNode initInsn, InsnNode incrInsn, IfCondition condition) {
|
||||
if (!(incrInsn instanceof ArithNode)) {
|
||||
return null;
|
||||
}
|
||||
ArithNode arithNode = (ArithNode) incrInsn;
|
||||
if (arithNode.getOp() != ArithOp.ADD) {
|
||||
return null;
|
||||
}
|
||||
InsnArg lit = incrInsn.getArg(1);
|
||||
if (!lit.isLiteral() || ((LiteralArg) lit).getLiteral() != 1) {
|
||||
return null;
|
||||
}
|
||||
if (initInsn.getType() != InsnType.CONST
|
||||
|| !initInsn.getArg(0).isLiteral()
|
||||
|| ((LiteralArg) initInsn.getArg(0)).getLiteral() != 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
InsnArg condArg = incrInsn.getArg(0);
|
||||
if (!condArg.isRegister()) {
|
||||
return null;
|
||||
}
|
||||
SSAVar sVar = ((RegisterArg) condArg).getSVar();
|
||||
List<RegisterArg> args = sVar.getUseList();
|
||||
if (args.size() != 3 || args.get(2) != condArg) {
|
||||
return null;
|
||||
}
|
||||
condArg = args.get(0);
|
||||
RegisterArg arrIndex = args.get(1);
|
||||
InsnNode arrGetInsn = arrIndex.getParentInsn();
|
||||
if (arrGetInsn == null || arrGetInsn.getType() != InsnType.AGET) {
|
||||
return null;
|
||||
}
|
||||
if (!condition.isCompare()) {
|
||||
return null;
|
||||
}
|
||||
Compare compare = condition.getCompare();
|
||||
if (compare.getOp() != IfOp.LT || compare.getA() != condArg) {
|
||||
return null;
|
||||
}
|
||||
InsnNode len;
|
||||
InsnArg bCondArg = compare.getB();
|
||||
if (bCondArg.isInsnWrap()) {
|
||||
len = ((InsnWrapArg) bCondArg).getWrapInsn();
|
||||
} else if (bCondArg.isRegister()) {
|
||||
len = ((RegisterArg) bCondArg).getAssignInsn();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
if (len == null || len.getType() != InsnType.ARRAY_LENGTH) {
|
||||
return null;
|
||||
}
|
||||
InsnArg arrayArg = len.getArg(0);
|
||||
if (!arrayArg.equals(arrGetInsn.getArg(0))) {
|
||||
return null;
|
||||
}
|
||||
RegisterArg iterVar = arrGetInsn.getResult();
|
||||
if (iterVar == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// array for each loop confirmed
|
||||
len.add(AFlag.SKIP);
|
||||
arrGetInsn.add(AFlag.SKIP);
|
||||
InstructionRemover.unbindInsn(mth, len);
|
||||
|
||||
// inline array variable
|
||||
CodeShrinker.shrinkMethod(mth);
|
||||
if (arrGetInsn.contains(AFlag.WRAPPED)) {
|
||||
InsnArg wrapArg = BlockUtils.searchWrappedInsnParent(mth, arrGetInsn);
|
||||
if (wrapArg != null) {
|
||||
wrapArg.getParentInsn().replaceArg(wrapArg, iterVar);
|
||||
} else {
|
||||
LOG.debug(" checkArrayForEach: Wrapped insn not found: {}, mth: {}", arrGetInsn, mth);
|
||||
}
|
||||
}
|
||||
return new ForEachLoop(iterVar, len.getArg(0));
|
||||
}
|
||||
|
||||
private static boolean checkIterableForEach(MethodNode mth, LoopRegion loopRegion, IfCondition condition) {
|
||||
List<RegisterArg> condArgs = condition.getRegisterArgs();
|
||||
if (condArgs.size() != 1) {
|
||||
return false;
|
||||
}
|
||||
RegisterArg iteratorArg = condArgs.get(0);
|
||||
SSAVar sVar = iteratorArg.getSVar();
|
||||
if (sVar == null || sVar.isUsedInPhi()) {
|
||||
return false;
|
||||
}
|
||||
List<RegisterArg> useList = sVar.getUseList();
|
||||
InsnNode assignInsn = iteratorArg.getAssignInsn();
|
||||
if (useList.size() != 2
|
||||
|| assignInsn == null
|
||||
|| !checkInvoke(assignInsn, null, "iterator()Ljava/util/Iterator;", 0)) {
|
||||
return false;
|
||||
}
|
||||
InsnArg iterableArg = assignInsn.getArg(0);
|
||||
InsnNode hasNextCall = useList.get(0).getParentInsn();
|
||||
InsnNode nextCall = useList.get(1).getParentInsn();
|
||||
if (!checkInvoke(hasNextCall, "java.util.Iterator", "hasNext()Z", 0)
|
||||
|| !checkInvoke(nextCall, "java.util.Iterator", "next()Ljava/lang/Object;", 0)) {
|
||||
return false;
|
||||
}
|
||||
List<InsnNode> toSkip = new LinkedList<InsnNode>();
|
||||
RegisterArg iterVar = nextCall.getResult();
|
||||
if (nextCall.contains(AFlag.WRAPPED)) {
|
||||
InsnArg wrapArg = BlockUtils.searchWrappedInsnParent(mth, nextCall);
|
||||
if (wrapArg != null) {
|
||||
InsnNode parentInsn = wrapArg.getParentInsn();
|
||||
if (parentInsn.getType() != InsnType.CHECK_CAST) {
|
||||
parentInsn.replaceArg(wrapArg, iterVar);
|
||||
} else {
|
||||
iterVar = parentInsn.getResult();
|
||||
InsnArg castArg = BlockUtils.searchWrappedInsnParent(mth, parentInsn);
|
||||
if (castArg != null) {
|
||||
castArg.getParentInsn().replaceArg(castArg, iterVar);
|
||||
} else {
|
||||
// cast not inlined
|
||||
toSkip.add(parentInsn);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LOG.warn(" checkIterableForEach: Wrapped insn not found: {}, mth: {}", nextCall, mth);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
toSkip.add(nextCall);
|
||||
}
|
||||
if (iterVar == null || !fixIterableType(iterableArg, iterVar)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
assignInsn.add(AFlag.SKIP);
|
||||
for (InsnNode insnNode : toSkip) {
|
||||
insnNode.add(AFlag.SKIP);
|
||||
}
|
||||
loopRegion.setType(new ForEachLoop(iterVar, iterableArg));
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean fixIterableType(InsnArg iterableArg, RegisterArg iterVar) {
|
||||
ArgType type = iterableArg.getType();
|
||||
if (type.isGeneric()) {
|
||||
ArgType[] genericTypes = type.getGenericTypes();
|
||||
if (genericTypes != null && genericTypes.length == 1) {
|
||||
ArgType gType = genericTypes[0];
|
||||
if (ArgType.isInstanceOf(gType, iterVar.getType())) {
|
||||
return true;
|
||||
} else {
|
||||
LOG.warn("Generic type differs: {} and {}", type, iterVar.getType());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!iterableArg.isRegister()) {
|
||||
return true;
|
||||
}
|
||||
// TODO: add checks
|
||||
type = ArgType.generic(type.getObject(), new ArgType[]{iterVar.getType()});
|
||||
iterableArg.setType(type);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if instruction is a interface invoke with corresponding parameters.
|
||||
*/
|
||||
private static boolean checkInvoke(InsnNode insn, String declClsFullName, String mthId, int argsCount) {
|
||||
if (insn.getType() == InsnType.INVOKE) {
|
||||
InvokeNode inv = (InvokeNode) insn;
|
||||
MethodInfo callMth = inv.getCallMth();
|
||||
if (callMth.getArgsCount() == argsCount
|
||||
&& callMth.getShortId().equals(mthId)
|
||||
&& inv.getInvokeType() == InvokeType.INTERFACE) {
|
||||
return declClsFullName == null || callMth.getDeclClass().getFullName().equals(declClsFullName);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean assignOnlyInLoop(MethodNode mth, LoopRegion loopRegion, RegisterArg arg) {
|
||||
InsnNode assignInsn = arg.getAssignInsn();
|
||||
if (assignInsn == null) {
|
||||
return true;
|
||||
}
|
||||
if (!argInLoop(mth, loopRegion, assignInsn.getResult())) {
|
||||
return false;
|
||||
}
|
||||
if (assignInsn instanceof PhiInsn) {
|
||||
PhiInsn phiInsn = (PhiInsn) assignInsn;
|
||||
for (InsnArg phiArg : phiInsn.getArguments()) {
|
||||
if (!assignOnlyInLoop(mth, loopRegion, (RegisterArg) phiArg)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean usedOnlyInLoop(MethodNode mth, LoopRegion loopRegion, RegisterArg arg) {
|
||||
List<RegisterArg> useList = arg.getSVar().getUseList();
|
||||
for (RegisterArg useArg : useList) {
|
||||
if (!argInLoop(mth, loopRegion, useArg)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean argInLoop(MethodNode mth, LoopRegion loopRegion, RegisterArg arg) {
|
||||
InsnNode parentInsn = arg.getParentInsn();
|
||||
if (parentInsn == null) {
|
||||
return false;
|
||||
}
|
||||
BlockNode block = BlockUtils.getBlockByInsn(mth, parentInsn);
|
||||
if (block == null) {
|
||||
LOG.debug(" LoopRegionVisitor: instruction not found: {}, mth: {}", parentInsn, mth);
|
||||
return false;
|
||||
}
|
||||
return RegionUtils.isRegionContainsBlock(loopRegion, block);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void leaveRegion(MethodNode mth, IRegion region) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processBlock(MethodNode mth, IBlock container) {
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user