Compare commits
73 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 913a5b5d0f | |||
| c594137c19 | |||
| b2f41e95bf | |||
| e733c91783 | |||
| 4e982722a5 | |||
| 2b1f815c58 | |||
| 0fff1a6754 | |||
| d95d268ec5 | |||
| b4930bc40c | |||
| 5f302238ad | |||
| 7cba2c3f81 | |||
| 218c39b1ec | |||
| e915f4fcd7 | |||
| bc9164b952 | |||
| 7c34be267f | |||
| 042464438c | |||
| cf68e4722a | |||
| 7be37ff76e | |||
| 1118236075 | |||
| ef8a685621 | |||
| e4fef402c9 | |||
| 5528afa404 | |||
| e3189fae37 | |||
| 6d963b378c | |||
| 895ddfa38f | |||
| 28e334a0ba | |||
| d060f5b877 | |||
| 7b70d617e0 | |||
| 261ba4645d | |||
| 2ab7524e71 | |||
| d55969bc65 | |||
| 9976894091 | |||
| 76a0608a04 | |||
| 0d93d335a1 | |||
| ffb9788047 | |||
| 5dd82eede9 | |||
| 14b90466ef | |||
| 43592c3e49 | |||
| b46093b3cc | |||
| 2b9c092705 | |||
| bc73010d4e | |||
| 2d8d416483 | |||
| f549a0691e | |||
| 96c2fb6f54 | |||
| f6d475292c | |||
| bd4d4f49ff | |||
| 5a24eac375 | |||
| a684118dbb | |||
| a324376e60 | |||
| 04e50afaba | |||
| 69494c9212 | |||
| b2f0f02541 | |||
| 71f249113d | |||
| 1d84c00161 | |||
| 5bc7e19a28 | |||
| c46703a05d | |||
| 129a7c39af | |||
| ac3f3e8385 | |||
| bc8ad4df86 | |||
| 53ac3ec582 | |||
| d2d43711c2 | |||
| 510035b7b7 | |||
| c923d19bcc | |||
| bff9597360 | |||
| 78b39a60e8 | |||
| 932966b6b8 | |||
| 85a18e6d75 | |||
| 5d86bf9788 | |||
| 406d9878d8 | |||
| 4e6c5cb27a | |||
| a9c0185bf5 | |||
| 0111172a03 | |||
| 57541488d3 |
@@ -0,0 +1,3 @@
|
||||
[submodule "jadx-test-app/test-app"]
|
||||
path = jadx-test-app/test-app
|
||||
url = git://github.com/skylot/jadx-test-app.git
|
||||
+5
-1
@@ -17,7 +17,11 @@ sudo: false
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.gradle
|
||||
- $HOME/.gradle/caches/
|
||||
- $HOME/.gradle/wrapper/
|
||||
|
||||
before_cache:
|
||||
- rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
|
||||
|
||||
notifications:
|
||||
email:
|
||||
|
||||
@@ -144,7 +144,8 @@ THE POSSIBILITY OF SUCH DAMAGE.
|
||||
Jadx-gui components
|
||||
===================
|
||||
|
||||
RSyntaxTextArea library licensed under modified BSD license:
|
||||
RSyntaxTextArea library (https://github.com/bobbylight/RSyntaxTextArea)
|
||||
licensed under modified BSD license:
|
||||
|
||||
*******************************************************************************
|
||||
Copyright (c) 2012, Robert Futrell
|
||||
@@ -174,8 +175,39 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*******************************************************************************
|
||||
|
||||
|
||||
Icons copied from several places:
|
||||
- Eclipse Project (JDT UI) - licensed under EPL v1.0 (http://www.eclipse.org/legal/epl-v10.html)
|
||||
- famfamfam silk icon set (http://www.famfamfam.com/lab/icons/silk/) - licensed under Creative Commons Attribution 2.5 License (http://creativecommons.org/licenses/by/2.5/)
|
||||
Concurrent Trees (https://code.google.com/p/concurrent-trees/)
|
||||
licenced under Apache License 2.0:
|
||||
|
||||
*******************************************************************************
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*******************************************************************************
|
||||
|
||||
|
||||
Image Viewer (https://github.com/kazocsaba/imageviewer)
|
||||
|
||||
*******************************************************************************
|
||||
Copyright (c) 2008-2012 Kazó Csaba
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*******************************************************************************
|
||||
|
||||
JFontChooser Component - http://sourceforge.jp/projects/jfontchooser/
|
||||
|
||||
Icons copied from several places:
|
||||
- Eclipse Project (JDT UI) - licensed under EPL v1.0 (http://www.eclipse.org/legal/epl-v10.html)
|
||||
- famfamfam silk icon set (http://www.famfamfam.com/lab/icons/silk/) - licensed
|
||||
under Creative Commons Attribution 2.5 License (http://creativecommons.org/licenses/by/2.5/)
|
||||
|
||||
@@ -42,20 +42,24 @@ Run **jadx** on itself:
|
||||
```
|
||||
jadx[-gui] [options] <input file> (.dex, .apk, .jar or .class)
|
||||
options:
|
||||
-d, --output-dir - output directory
|
||||
-j, --threads-count - processing threads count
|
||||
-f, --fallback - make simple dump (using goto instead of 'if', 'for', etc)
|
||||
-r, --no-res - do not decode resources
|
||||
-s, --no-src - do not decompile source code
|
||||
--show-bad-code - show inconsistent code (incorrectly decompiled)
|
||||
--cfg - save methods control flow graph to dot file
|
||||
--raw-cfg - save methods control flow graph (use raw instructions)
|
||||
-v, --verbose - verbose output
|
||||
--deobf - activate deobfuscation
|
||||
--deobf-min - min length of name
|
||||
--deobf-max - max length of name
|
||||
--deobf-rewrite-cfg - force to save deobfuscation map
|
||||
-h, --help - print this help
|
||||
-d, --output-dir - output directory
|
||||
-j, --threads-count - processing threads count
|
||||
-r, --no-res - do not decode resources
|
||||
-s, --no-src - do not decompile source code
|
||||
-e, --export-gradle - save as android gradle project
|
||||
--show-bad-code - show inconsistent code (incorrectly decompiled)
|
||||
--no-replace-consts - don't replace constant value with matching constant field
|
||||
--escape-unicode - escape non latin characters in strings (with \u)
|
||||
--deobf - activate deobfuscation
|
||||
--deobf-min - min length of name
|
||||
--deobf-max - max length of name
|
||||
--deobf-rewrite-cfg - force to save deobfuscation map
|
||||
--deobf-use-sourcename - use source file name as class name alias
|
||||
--cfg - save methods control flow graph to dot file
|
||||
--raw-cfg - save methods control flow graph (use raw instructions)
|
||||
-f, --fallback - make simple dump (using goto instead of 'if', 'for', etc)
|
||||
-v, --verbose - verbose output
|
||||
-h, --help - print this help
|
||||
Example:
|
||||
jadx -d out classes.dex
|
||||
```
|
||||
|
||||
+4
-11
@@ -11,8 +11,6 @@ plugins {
|
||||
// id "com.github.ben-manes.versions" version "0.8"
|
||||
}
|
||||
|
||||
apply plugin: 'sonar-runner'
|
||||
|
||||
ext.jadxVersion = file('version').readLines().get(0)
|
||||
version = jadxVersion
|
||||
|
||||
@@ -65,14 +63,6 @@ subprojects {
|
||||
}
|
||||
}
|
||||
|
||||
/* Sonar runner configuration */
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
sonarRunner {
|
||||
toolVersion = '2.4'
|
||||
}
|
||||
|
||||
task copyArtifacts(type: Sync, dependsOn: ['jadx-cli:installDist', 'jadx-gui:installDist']) {
|
||||
destinationDir file("$buildDir/jadx")
|
||||
['jadx-cli', 'jadx-gui'].each {
|
||||
@@ -93,6 +83,9 @@ task dist(dependsOn: pack) {
|
||||
task samples(dependsOn: 'jadx-samples:samples') {
|
||||
}
|
||||
|
||||
task testAppCheck(dependsOn: 'jadx-test-app:testAppCheck') {
|
||||
}
|
||||
|
||||
task pitest(overwrite: true, dependsOn: 'jadx-core:pitest') {
|
||||
}
|
||||
|
||||
@@ -105,5 +98,5 @@ build.dependsOn(dist, samples)
|
||||
clean.dependsOn(cleanBuildDir)
|
||||
|
||||
task wrapper(type: Wrapper) {
|
||||
gradleVersion = '2.3'
|
||||
gradleVersion = '2.7'
|
||||
}
|
||||
|
||||
+1
-1
@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-2.3-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-2.7-bin.zip
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package jadx.cli;
|
||||
|
||||
import ch.qos.logback.classic.spi.ILoggingEvent;
|
||||
import ch.qos.logback.core.Appender;
|
||||
import jadx.api.IJadxArgs;
|
||||
import jadx.api.JadxDecompiler;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
@@ -15,6 +17,7 @@ import java.util.Map;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.beust.jcommander.IStringConverter;
|
||||
import com.beust.jcommander.JCommander;
|
||||
import com.beust.jcommander.Parameter;
|
||||
import com.beust.jcommander.ParameterDescription;
|
||||
@@ -29,10 +32,7 @@ public class JadxCLIArgs implements IJadxArgs {
|
||||
protected String outDirName;
|
||||
|
||||
@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)")
|
||||
protected boolean fallbackMode = false;
|
||||
protected int threadsCount = Math.max(1, Runtime.getRuntime().availableProcessors() / 2);
|
||||
|
||||
@Parameter(names = {"-r", "--no-res"}, description = "do not decode resources")
|
||||
protected boolean skipResources = false;
|
||||
@@ -40,17 +40,18 @@ public class JadxCLIArgs implements IJadxArgs {
|
||||
@Parameter(names = {"-s", "--no-src"}, description = "do not decompile source code")
|
||||
protected boolean skipSources = false;
|
||||
|
||||
@Parameter(names = {"-e", "--export-gradle"}, description = "save as android gradle project")
|
||||
protected boolean exportAsGradleProject = 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;
|
||||
@Parameter(names = "--no-replace-consts", converter = InvertedBooleanConverter.class,
|
||||
description = "don't replace constant value with matching constant field")
|
||||
protected boolean replaceConsts = true;
|
||||
|
||||
@Parameter(names = {"--raw-cfg"}, description = "save methods control flow graph (use raw instructions)")
|
||||
protected boolean rawCfgOutput = false;
|
||||
|
||||
@Parameter(names = {"-v", "--verbose"}, description = "verbose output")
|
||||
protected boolean verbose = false;
|
||||
@Parameter(names = {"--escape-unicode"}, description = "escape non latin characters in strings (with \\u)")
|
||||
protected boolean escapeUnicode = false;
|
||||
|
||||
@Parameter(names = {"--deobf"}, description = "activate deobfuscation")
|
||||
protected boolean deobfuscationOn = false;
|
||||
@@ -64,6 +65,21 @@ public class JadxCLIArgs implements IJadxArgs {
|
||||
@Parameter(names = {"--deobf-rewrite-cfg"}, description = "force to save deobfuscation map")
|
||||
protected boolean deobfuscationForceSave = false;
|
||||
|
||||
@Parameter(names = {"--deobf-use-sourcename"}, description = "use source file name as class name alias")
|
||||
protected boolean deobfuscationUseSourceNameAsAlias = false;
|
||||
|
||||
@Parameter(names = {"--cfg"}, description = "save methods control flow graph to dot file")
|
||||
protected boolean cfgOutput = false;
|
||||
|
||||
@Parameter(names = {"--raw-cfg"}, description = "save methods control flow graph (use raw instructions)")
|
||||
protected boolean rawCfgOutput = false;
|
||||
|
||||
@Parameter(names = {"-f", "--fallback"}, description = "make simple dump (using goto instead of 'if', 'for', etc)")
|
||||
protected boolean fallbackMode = false;
|
||||
|
||||
@Parameter(names = {"-v", "--verbose"}, description = "verbose output")
|
||||
protected boolean verbose = false;
|
||||
|
||||
@Parameter(names = {"-h", "--help"}, description = "print this help", help = true)
|
||||
protected boolean printHelp = false;
|
||||
|
||||
@@ -113,7 +129,11 @@ public class JadxCLIArgs implements IJadxArgs {
|
||||
if (isVerbose()) {
|
||||
ch.qos.logback.classic.Logger rootLogger =
|
||||
(ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
|
||||
rootLogger.setLevel(ch.qos.logback.classic.Level.DEBUG);
|
||||
// remove INFO ThresholdFilter
|
||||
Appender<ILoggingEvent> appender = rootLogger.getAppender("STDOUT");
|
||||
if (appender != null) {
|
||||
appender.clearAllFilters();
|
||||
}
|
||||
}
|
||||
} catch (JadxException e) {
|
||||
System.err.println("ERROR: " + e.getMessage());
|
||||
@@ -166,6 +186,13 @@ public class JadxCLIArgs implements IJadxArgs {
|
||||
}
|
||||
}
|
||||
|
||||
public static class InvertedBooleanConverter implements IStringConverter<Boolean> {
|
||||
@Override
|
||||
public Boolean convert(String value) {
|
||||
return "false".equals(value);
|
||||
}
|
||||
}
|
||||
|
||||
public List<File> getInput() {
|
||||
return input;
|
||||
}
|
||||
@@ -242,4 +269,24 @@ public class JadxCLIArgs implements IJadxArgs {
|
||||
public boolean isDeobfuscationForceSave() {
|
||||
return deobfuscationForceSave;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean useSourceNameAsClassAlias() {
|
||||
return deobfuscationUseSourceNameAsAlias;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean escapeUnicode() {
|
||||
return escapeUnicode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReplaceConsts() {
|
||||
return replaceConsts;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isExportAsGradleProject() {
|
||||
return exportAsGradleProject;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
<configuration>
|
||||
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
|
||||
<level>INFO</level>
|
||||
</filter>
|
||||
<encoder>
|
||||
<pattern>%-5level - %msg%n</pattern>
|
||||
<pattern>%d{HH:mm:ss} %-5level - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="INFO">
|
||||
<root level="DEBUG">
|
||||
<appender-ref ref="STDOUT"/>
|
||||
</root>
|
||||
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package jadx.cli;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
public class JadxCLIArgsTest {
|
||||
|
||||
@Test
|
||||
public void testInvertedBooleanOption() throws Exception {
|
||||
assertThat(parse("--no-replace-consts").isReplaceConsts(), is(false));
|
||||
assertThat(parse("").isReplaceConsts(), is(true));
|
||||
}
|
||||
|
||||
private JadxCLIArgs parse(String... args) {
|
||||
JadxCLIArgs jadxArgs = new JadxCLIArgs();
|
||||
boolean res = jadxArgs.processArgs(args);
|
||||
assertThat(res, is(true));
|
||||
return jadxArgs;
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ dependencies {
|
||||
compile 'commons-io:commons-io:2.4'
|
||||
compile 'org.ow2.asm:asm:5.0.3'
|
||||
compile 'com.intellij:annotations:12.0'
|
||||
compile 'uk.com.robust-it:cloning:1.9.2'
|
||||
|
||||
testCompile 'org.smali:smali:2.0.3'
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -2,24 +2,32 @@ package jadx.api;
|
||||
|
||||
public final class CodePosition {
|
||||
|
||||
private final JavaClass cls;
|
||||
private final JavaNode node;
|
||||
private final int line;
|
||||
private final int offset;
|
||||
|
||||
public CodePosition(JavaClass cls, int line, int offset) {
|
||||
this.cls = cls;
|
||||
public CodePosition(JavaNode node, int line, int offset) {
|
||||
this.node = node;
|
||||
this.line = line;
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
public CodePosition(int line, int offset) {
|
||||
this.cls = null;
|
||||
this.node = null;
|
||||
this.line = line;
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
public JavaNode getNode() {
|
||||
return node;
|
||||
}
|
||||
|
||||
public JavaClass getJavaClass() {
|
||||
return cls;
|
||||
JavaClass parent = node.getDeclaringClass();
|
||||
if (parent == null && node instanceof JavaClass) {
|
||||
return (JavaClass) node;
|
||||
}
|
||||
return parent;
|
||||
}
|
||||
|
||||
public int getLine() {
|
||||
@@ -30,10 +38,6 @@ public final class CodePosition {
|
||||
return offset;
|
||||
}
|
||||
|
||||
public boolean isSet() {
|
||||
return line != 0 || offset != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
@@ -53,6 +57,6 @@ public final class CodePosition {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return line + ":" + offset + (cls != null ? " " + cls : "");
|
||||
return line + ":" + offset + (node != null ? " " + node : "");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
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();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCFGOutput() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRawCFGOutput() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFallbackMode() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isShowInconsistentCode() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVerbose() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSkipResources() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSkipSources() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDeobfuscationOn() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDeobfuscationMinLength() {
|
||||
return Integer.MIN_VALUE + 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDeobfuscationMaxLength() {
|
||||
return Integer.MAX_VALUE - 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDeobfuscationForceSave() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -28,4 +28,18 @@ public interface IJadxArgs {
|
||||
int getDeobfuscationMaxLength();
|
||||
|
||||
boolean isDeobfuscationForceSave();
|
||||
|
||||
boolean useSourceNameAsClassAlias();
|
||||
|
||||
boolean escapeUnicode();
|
||||
|
||||
/**
|
||||
* Replace constant values with static final fields with same value
|
||||
*/
|
||||
boolean isReplaceConsts();
|
||||
|
||||
/**
|
||||
* Save as gradle project
|
||||
*/
|
||||
boolean isExportAsGradleProject();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
package jadx.api;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class JadxArgs implements IJadxArgs {
|
||||
|
||||
private File outDir = new File("jadx-output");
|
||||
private int threadsCount = Math.max(1, Runtime.getRuntime().availableProcessors() - 1);
|
||||
|
||||
private boolean cfgOutput = false;
|
||||
private boolean rawCFGOutput = false;
|
||||
|
||||
private boolean isVerbose = false;
|
||||
private boolean fallbackMode = false;
|
||||
private boolean showInconsistentCode = false;
|
||||
|
||||
private boolean isSkipResources = false;
|
||||
private boolean isSkipSources = false;
|
||||
|
||||
private boolean isDeobfuscationOn = false;
|
||||
private boolean isDeobfuscationForceSave = false;
|
||||
private boolean useSourceNameAsClassAlias = false;
|
||||
|
||||
private int deobfuscationMinLength = 0;
|
||||
private int deobfuscationMaxLength = Integer.MAX_VALUE;
|
||||
|
||||
private boolean escapeUnicode = false;
|
||||
private boolean replaceConsts = true;
|
||||
private boolean exportAsGradleProject = false;
|
||||
|
||||
@Override
|
||||
public File getOutDir() {
|
||||
return outDir;
|
||||
}
|
||||
|
||||
public void setOutDir(File outDir) {
|
||||
this.outDir = outDir;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getThreadsCount() {
|
||||
return threadsCount;
|
||||
}
|
||||
|
||||
public void setThreadsCount(int threadsCount) {
|
||||
this.threadsCount = threadsCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCFGOutput() {
|
||||
return cfgOutput;
|
||||
}
|
||||
|
||||
public void setCfgOutput(boolean cfgOutput) {
|
||||
this.cfgOutput = cfgOutput;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRawCFGOutput() {
|
||||
return rawCFGOutput;
|
||||
}
|
||||
|
||||
public void setRawCFGOutput(boolean rawCFGOutput) {
|
||||
this.rawCFGOutput = rawCFGOutput;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFallbackMode() {
|
||||
return fallbackMode;
|
||||
}
|
||||
|
||||
public void setFallbackMode(boolean fallbackMode) {
|
||||
this.fallbackMode = fallbackMode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isShowInconsistentCode() {
|
||||
return showInconsistentCode;
|
||||
}
|
||||
|
||||
public void setShowInconsistentCode(boolean showInconsistentCode) {
|
||||
this.showInconsistentCode = showInconsistentCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVerbose() {
|
||||
return isVerbose;
|
||||
}
|
||||
|
||||
public void setVerbose(boolean verbose) {
|
||||
isVerbose = verbose;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSkipResources() {
|
||||
return isSkipResources;
|
||||
}
|
||||
|
||||
public void setSkipResources(boolean skipResources) {
|
||||
isSkipResources = skipResources;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSkipSources() {
|
||||
return isSkipSources;
|
||||
}
|
||||
|
||||
public void setSkipSources(boolean skipSources) {
|
||||
isSkipSources = skipSources;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDeobfuscationOn() {
|
||||
return isDeobfuscationOn;
|
||||
}
|
||||
|
||||
public void setDeobfuscationOn(boolean deobfuscationOn) {
|
||||
isDeobfuscationOn = deobfuscationOn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDeobfuscationForceSave() {
|
||||
return isDeobfuscationForceSave;
|
||||
}
|
||||
|
||||
public void setDeobfuscationForceSave(boolean deobfuscationForceSave) {
|
||||
isDeobfuscationForceSave = deobfuscationForceSave;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean useSourceNameAsClassAlias() {
|
||||
return useSourceNameAsClassAlias;
|
||||
}
|
||||
|
||||
public void setUseSourceNameAsClassAlias(boolean useSourceNameAsClassAlias) {
|
||||
this.useSourceNameAsClassAlias = useSourceNameAsClassAlias;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDeobfuscationMinLength() {
|
||||
return deobfuscationMinLength;
|
||||
}
|
||||
|
||||
public void setDeobfuscationMinLength(int deobfuscationMinLength) {
|
||||
this.deobfuscationMinLength = deobfuscationMinLength;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDeobfuscationMaxLength() {
|
||||
return deobfuscationMaxLength;
|
||||
}
|
||||
|
||||
public void setDeobfuscationMaxLength(int deobfuscationMaxLength) {
|
||||
this.deobfuscationMaxLength = deobfuscationMaxLength;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean escapeUnicode() {
|
||||
return escapeUnicode;
|
||||
}
|
||||
|
||||
public void setEscapeUnicode(boolean escapeUnicode) {
|
||||
this.escapeUnicode = escapeUnicode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReplaceConsts() {
|
||||
return replaceConsts;
|
||||
}
|
||||
|
||||
public void setReplaceConsts(boolean replaceConsts) {
|
||||
this.replaceConsts = replaceConsts;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isExportAsGradleProject() {
|
||||
return exportAsGradleProject;
|
||||
}
|
||||
|
||||
public void setExportAsGradleProject(boolean exportAsGradleProject) {
|
||||
this.exportAsGradleProject = exportAsGradleProject;
|
||||
}
|
||||
}
|
||||
@@ -3,16 +3,20 @@ package jadx.api;
|
||||
import jadx.core.Jadx;
|
||||
import jadx.core.ProcessClass;
|
||||
import jadx.core.codegen.CodeGen;
|
||||
import jadx.core.codegen.CodeWriter;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.dex.visitors.IDexTreeVisitor;
|
||||
import jadx.core.dex.visitors.SaveCode;
|
||||
import jadx.core.export.ExportGradleProject;
|
||||
import jadx.core.utils.exceptions.DecodeException;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
import jadx.core.utils.files.InputFile;
|
||||
import jadx.core.xmlgen.BinaryXMLParser;
|
||||
import jadx.core.xmlgen.ResourcesSaver;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@@ -62,8 +66,12 @@ public final class JadxDecompiler {
|
||||
|
||||
private BinaryXMLParser xmlParser;
|
||||
|
||||
private Map<ClassNode, JavaClass> classesMap = new HashMap<ClassNode, JavaClass>();
|
||||
private Map<MethodNode, JavaMethod> methodsMap = new HashMap<MethodNode, JavaMethod>();
|
||||
private Map<FieldNode, JavaField> fieldsMap = new HashMap<FieldNode, JavaField>();
|
||||
|
||||
public JadxDecompiler() {
|
||||
this(new DefaultJadxArgs());
|
||||
this(new JadxArgs());
|
||||
}
|
||||
|
||||
public JadxDecompiler(IJadxArgs jadxArgs) {
|
||||
@@ -80,7 +88,7 @@ public final class JadxDecompiler {
|
||||
|
||||
void init() {
|
||||
if (outDir == null) {
|
||||
outDir = new DefaultJadxArgs().getOutDir();
|
||||
outDir = new JadxArgs().getOutDir();
|
||||
}
|
||||
this.passes = Jadx.getPassesList(args, outDir);
|
||||
this.codeGen = new CodeGen(args);
|
||||
@@ -110,7 +118,7 @@ public final class JadxDecompiler {
|
||||
inputFiles.clear();
|
||||
for (File file : files) {
|
||||
try {
|
||||
inputFiles.add(new InputFile(file));
|
||||
InputFile.addFilesFrom(file, inputFiles);
|
||||
} catch (IOException e) {
|
||||
throw new JadxException("Error load file: " + file, e);
|
||||
}
|
||||
@@ -153,35 +161,48 @@ public final class JadxDecompiler {
|
||||
|
||||
LOG.info("processing ...");
|
||||
ExecutorService executor = Executors.newFixedThreadPool(threadsCount);
|
||||
|
||||
File sourcesOutDir;
|
||||
File resOutDir;
|
||||
if (args.isExportAsGradleProject()) {
|
||||
ExportGradleProject export = new ExportGradleProject(root, outDir);
|
||||
export.init();
|
||||
sourcesOutDir = export.getSrcOutDir();
|
||||
resOutDir = export.getResOutDir();
|
||||
} else {
|
||||
sourcesOutDir = outDir;
|
||||
resOutDir = outDir;
|
||||
}
|
||||
if (saveSources) {
|
||||
for (final JavaClass cls : getClasses()) {
|
||||
executor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
cls.decompile();
|
||||
SaveCode.save(outDir, args, cls.getClassNode());
|
||||
}
|
||||
});
|
||||
}
|
||||
appendSourcesSave(executor, sourcesOutDir);
|
||||
}
|
||||
if (saveResources) {
|
||||
for (final ResourceFile resourceFile : getResources()) {
|
||||
executor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (ResourceType.isSupportedForUnpack(resourceFile.getType())) {
|
||||
CodeWriter cw = resourceFile.getContent();
|
||||
if (cw != null) {
|
||||
cw.save(new File(outDir, resourceFile.getName()));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
appendResourcesSave(executor, resOutDir);
|
||||
}
|
||||
return executor;
|
||||
}
|
||||
|
||||
private void appendResourcesSave(ExecutorService executor, File outDir) {
|
||||
for (ResourceFile resourceFile : getResources()) {
|
||||
executor.execute(new ResourcesSaver(outDir, resourceFile));
|
||||
}
|
||||
}
|
||||
|
||||
private void appendSourcesSave(ExecutorService executor, final File outDir) {
|
||||
for (final JavaClass cls : getClasses()) {
|
||||
if (cls.getClassNode().contains(AFlag.DONT_GENERATE)) {
|
||||
continue;
|
||||
}
|
||||
executor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
cls.decompile();
|
||||
SaveCode.save(outDir, args, cls.getClassNode());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public List<JavaClass> getClasses() {
|
||||
if (root == null) {
|
||||
return Collections.emptyList();
|
||||
@@ -189,8 +210,11 @@ public final class JadxDecompiler {
|
||||
if (classes == null) {
|
||||
List<ClassNode> classNodeList = root.getClasses(false);
|
||||
List<JavaClass> clsList = new ArrayList<JavaClass>(classNodeList.size());
|
||||
classesMap.clear();
|
||||
for (ClassNode classNode : classNodeList) {
|
||||
clsList.add(new JavaClass(classNode, this));
|
||||
JavaClass javaClass = new JavaClass(classNode, this);
|
||||
clsList.add(javaClass);
|
||||
classesMap.put(classNode, javaClass);
|
||||
}
|
||||
classes = Collections.unmodifiableList(clsList);
|
||||
}
|
||||
@@ -249,6 +273,7 @@ public final class JadxDecompiler {
|
||||
if (root == null) {
|
||||
return;
|
||||
}
|
||||
root.getClsp().printMissingClasses();
|
||||
root.getErrorsCounter().printReport();
|
||||
}
|
||||
|
||||
@@ -285,23 +310,23 @@ public final class JadxDecompiler {
|
||||
return root;
|
||||
}
|
||||
|
||||
BinaryXMLParser getXmlParser() {
|
||||
synchronized BinaryXMLParser getXmlParser() {
|
||||
if (xmlParser == null) {
|
||||
xmlParser = new BinaryXMLParser(root);
|
||||
}
|
||||
return xmlParser;
|
||||
}
|
||||
|
||||
JavaClass findJavaClass(ClassNode cls) {
|
||||
if (cls == null) {
|
||||
return null;
|
||||
}
|
||||
for (JavaClass javaClass : getClasses()) {
|
||||
if (javaClass.getClassNode().equals(cls)) {
|
||||
return javaClass;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
Map<ClassNode, JavaClass> getClassesMap() {
|
||||
return classesMap;
|
||||
}
|
||||
|
||||
Map<MethodNode, JavaMethod> getMethodsMap() {
|
||||
return methodsMap;
|
||||
}
|
||||
|
||||
Map<FieldNode, JavaField> getFieldsMap() {
|
||||
return fieldsMap;
|
||||
}
|
||||
|
||||
public IJadxArgs getArgs() {
|
||||
@@ -312,4 +337,5 @@ public final class JadxDecompiler {
|
||||
public String toString() {
|
||||
return "jadx decompiler " + getVersion();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -11,9 +11,12 @@ import jadx.core.dex.nodes.MethodNode;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public final class JavaClass implements JavaNode {
|
||||
|
||||
private final JadxDecompiler decompiler;
|
||||
@@ -44,14 +47,14 @@ public final class JavaClass implements JavaNode {
|
||||
if (code == null) {
|
||||
decompile();
|
||||
code = cls.getCode();
|
||||
if (code == null) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
if (code == null) {
|
||||
return "";
|
||||
}
|
||||
return code.toString();
|
||||
return code.getCodeStr();
|
||||
}
|
||||
|
||||
public void decompile() {
|
||||
public synchronized void decompile() {
|
||||
if (decompiler == null) {
|
||||
return;
|
||||
}
|
||||
@@ -66,6 +69,7 @@ public final class JavaClass implements JavaNode {
|
||||
}
|
||||
|
||||
private void load() {
|
||||
JadxDecompiler rootDecompiler = getRootDecompiler();
|
||||
int inClsCount = cls.getInnerClasses().size();
|
||||
if (inClsCount != 0) {
|
||||
List<JavaClass> list = new ArrayList<JavaClass>(inClsCount);
|
||||
@@ -74,6 +78,7 @@ public final class JavaClass implements JavaNode {
|
||||
JavaClass javaClass = new JavaClass(inner, this);
|
||||
javaClass.load();
|
||||
list.add(javaClass);
|
||||
rootDecompiler.getClassesMap().put(inner, javaClass);
|
||||
}
|
||||
}
|
||||
this.innerClasses = Collections.unmodifiableList(list);
|
||||
@@ -84,7 +89,9 @@ public final class JavaClass implements JavaNode {
|
||||
List<JavaField> flds = new ArrayList<JavaField>(fieldsCount);
|
||||
for (FieldNode f : cls.getFields()) {
|
||||
if (!f.contains(AFlag.DONT_GENERATE)) {
|
||||
flds.add(new JavaField(f, this));
|
||||
JavaField javaField = new JavaField(f, this);
|
||||
flds.add(javaField);
|
||||
rootDecompiler.getFieldsMap().put(f, javaField);
|
||||
}
|
||||
}
|
||||
this.fields = Collections.unmodifiableList(flds);
|
||||
@@ -95,7 +102,9 @@ public final class JavaClass implements JavaNode {
|
||||
List<JavaMethod> mths = new ArrayList<JavaMethod>(methodsCount);
|
||||
for (MethodNode m : cls.getMethods()) {
|
||||
if (!m.contains(AFlag.DONT_GENERATE)) {
|
||||
mths.add(new JavaMethod(this, m));
|
||||
JavaMethod javaMethod = new JavaMethod(this, m);
|
||||
mths.add(javaMethod);
|
||||
rootDecompiler.getMethodsMap().put(m, javaMethod);
|
||||
}
|
||||
}
|
||||
Collections.sort(mths, new Comparator<JavaMethod>() {
|
||||
@@ -108,38 +117,81 @@ public final class JavaClass implements JavaNode {
|
||||
}
|
||||
}
|
||||
|
||||
private JadxDecompiler getRootDecompiler() {
|
||||
if (parent != null) {
|
||||
return parent.getRootDecompiler();
|
||||
}
|
||||
return decompiler;
|
||||
}
|
||||
|
||||
private Map<CodePosition, Object> getCodeAnnotations() {
|
||||
decompile();
|
||||
return cls.getCode().getAnnotations();
|
||||
}
|
||||
|
||||
public CodePosition getDefinitionPosition(int line, int offset) {
|
||||
public Map<CodePosition, JavaNode> getUsageMap() {
|
||||
Map<CodePosition, Object> map = getCodeAnnotations();
|
||||
if (map.isEmpty() || decompiler == null) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
Map<CodePosition, JavaNode> resultMap = new HashMap<CodePosition, JavaNode>(map.size());
|
||||
for (Map.Entry<CodePosition, Object> entry : map.entrySet()) {
|
||||
CodePosition codePosition = entry.getKey();
|
||||
Object obj = entry.getValue();
|
||||
if (obj instanceof LineAttrNode) {
|
||||
JavaNode node = convertNode(obj);
|
||||
if (node != null) {
|
||||
resultMap.put(codePosition, node);
|
||||
}
|
||||
}
|
||||
}
|
||||
return resultMap;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private JavaNode convertNode(Object obj) {
|
||||
if (!(obj instanceof LineAttrNode)) {
|
||||
return null;
|
||||
}
|
||||
if (obj instanceof ClassNode) {
|
||||
return getRootDecompiler().getClassesMap().get(obj);
|
||||
}
|
||||
if (obj instanceof MethodNode) {
|
||||
return getRootDecompiler().getMethodsMap().get(obj);
|
||||
}
|
||||
if (obj instanceof FieldNode) {
|
||||
return getRootDecompiler().getFieldsMap().get(obj);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public JavaNode getJavaNodeAtPosition(int line, int offset) {
|
||||
Map<CodePosition, Object> map = getCodeAnnotations();
|
||||
if (map.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
Object obj = map.get(new CodePosition(line, offset));
|
||||
if (!(obj instanceof LineAttrNode)) {
|
||||
if (obj == 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 convertNode(obj);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public CodePosition getDefinitionPosition(int line, int offset) {
|
||||
JavaNode javaNode = getJavaNodeAtPosition(line, offset);
|
||||
if (javaNode == null) {
|
||||
return null;
|
||||
}
|
||||
return getDefinitionPosition(javaNode);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public CodePosition getDefinitionPosition(JavaNode javaNode) {
|
||||
JavaClass jCls = javaNode.getTopParentClass();
|
||||
jCls.decompile();
|
||||
int defLine = ((LineAttrNode) obj).getDecompiledLine();
|
||||
int defLine = javaNode.getDecompiledLine();
|
||||
if (defLine == 0) {
|
||||
return null;
|
||||
}
|
||||
@@ -170,6 +222,11 @@ public final class JavaClass implements JavaNode {
|
||||
return parent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaClass getTopParentClass() {
|
||||
return parent == null ? this : parent.getTopParentClass();
|
||||
}
|
||||
|
||||
public AccessInfo getAccessInfo() {
|
||||
return cls.getAccessFlags();
|
||||
}
|
||||
|
||||
@@ -29,6 +29,11 @@ public final class JavaField implements JavaNode {
|
||||
return parent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaClass getTopParentClass() {
|
||||
return parent.getTopParentClass();
|
||||
}
|
||||
|
||||
public AccessInfo getAccessFlags() {
|
||||
return field.getAccessFlags();
|
||||
}
|
||||
@@ -40,4 +45,19 @@ public final class JavaField implements JavaNode {
|
||||
public int getDecompiledLine() {
|
||||
return field.getDecompiledLine();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return field.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return this == o || o instanceof JavaField && field.equals(((JavaField) o).field);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return field.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,11 @@ public final class JavaMethod implements JavaNode {
|
||||
return parent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaClass getTopParentClass() {
|
||||
return parent.getTopParentClass();
|
||||
}
|
||||
|
||||
public AccessInfo getAccessFlags() {
|
||||
return mth.getAccessFlags();
|
||||
}
|
||||
@@ -53,4 +58,19 @@ public final class JavaMethod implements JavaNode {
|
||||
public int getDecompiledLine() {
|
||||
return mth.getDecompiledLine();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return mth.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return this == o || o instanceof JavaMethod && mth.equals(((JavaMethod) o).mth);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return mth.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,4 +7,8 @@ public interface JavaNode {
|
||||
String getFullName();
|
||||
|
||||
JavaClass getDeclaringClass();
|
||||
|
||||
JavaClass getTopParentClass();
|
||||
|
||||
int getDecompiledLine();
|
||||
}
|
||||
|
||||
@@ -33,6 +33,16 @@ public final class JavaPackage implements JavaNode, Comparable<JavaPackage> {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaClass getTopParentClass() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDecompiledLine() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull JavaPackage o) {
|
||||
return name.compareTo(o.name);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package jadx.api;
|
||||
|
||||
import jadx.core.codegen.CodeWriter;
|
||||
import jadx.core.xmlgen.ResContainer;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
@@ -48,7 +48,7 @@ public class ResourceFile {
|
||||
return type;
|
||||
}
|
||||
|
||||
public CodeWriter getContent() {
|
||||
public ResContainer loadContent() {
|
||||
return ResourcesLoader.loadContent(decompiler, this);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package jadx.api;
|
||||
|
||||
import jadx.core.codegen.CodeWriter;
|
||||
import jadx.core.xmlgen.ResContainer;
|
||||
|
||||
public class ResourceFileContent extends ResourceFile {
|
||||
|
||||
private final CodeWriter content;
|
||||
|
||||
public ResourceFileContent(String name, ResourceType type, CodeWriter content) {
|
||||
super(null, name, type);
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResContainer loadContent() {
|
||||
return ResContainer.singleFile(getName(), content);
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
package jadx.api;
|
||||
|
||||
public enum ResourceType {
|
||||
CODE(".dex", ".class"),
|
||||
CODE(".dex", ".jar", ".class"),
|
||||
MANIFEST("AndroidManifest.xml"),
|
||||
XML(".xml"), // TODO binary or not?
|
||||
ARSC(".arsc"), // TODO decompile !!!
|
||||
XML(".xml"),
|
||||
ARSC(".arsc"),
|
||||
FONT(".ttf"),
|
||||
IMG(".png", ".gif", ".jpg"),
|
||||
LIB(".so"),
|
||||
@@ -34,15 +34,15 @@ public enum ResourceType {
|
||||
public static boolean isSupportedForUnpack(ResourceType type) {
|
||||
switch (type) {
|
||||
case CODE:
|
||||
case ARSC:
|
||||
case LIB:
|
||||
case FONT:
|
||||
case IMG:
|
||||
case UNKNOWN:
|
||||
return false;
|
||||
|
||||
case MANIFEST:
|
||||
case XML:
|
||||
case ARSC:
|
||||
case IMG:
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -5,6 +5,7 @@ import jadx.core.codegen.CodeWriter;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
import jadx.core.utils.files.InputFile;
|
||||
import jadx.core.xmlgen.ResContainer;
|
||||
import jadx.core.xmlgen.ResTableParser;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
@@ -21,11 +22,14 @@ import java.util.zip.ZipFile;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import static jadx.core.utils.files.FileUtils.READ_BUFFER_SIZE;
|
||||
import static jadx.core.utils.files.FileUtils.close;
|
||||
import static jadx.core.utils.files.FileUtils.copyStream;
|
||||
|
||||
// TODO: move to core package
|
||||
public final class ResourcesLoader {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ResourcesLoader.class);
|
||||
|
||||
private static final int READ_BUFFER_SIZE = 8 * 1024;
|
||||
private static final int LOAD_SIZE_LIMIT = 10 * 1024 * 1024;
|
||||
|
||||
private final JadxDecompiler jadxRef;
|
||||
@@ -43,16 +47,17 @@ public final class ResourcesLoader {
|
||||
}
|
||||
|
||||
public interface ResourceDecoder {
|
||||
Object decode(long size, InputStream is) throws IOException;
|
||||
ResContainer decode(long size, InputStream is) throws IOException;
|
||||
}
|
||||
|
||||
public static Object decodeStream(ResourceFile rf, ResourceDecoder decoder) throws JadxException {
|
||||
public static ResContainer decodeStream(ResourceFile rf, ResourceDecoder decoder) throws JadxException {
|
||||
ZipRef zipRef = rf.getZipRef();
|
||||
if (zipRef == null) {
|
||||
return null;
|
||||
}
|
||||
ZipFile zipFile = null;
|
||||
InputStream inputStream = null;
|
||||
ResContainer result = null;
|
||||
try {
|
||||
zipFile = new ZipFile(zipRef.getZipFile());
|
||||
ZipEntry entry = zipFile.getEntry(zipRef.getEntryName());
|
||||
@@ -60,7 +65,7 @@ public final class ResourcesLoader {
|
||||
throw new IOException("Zip entry not found: " + zipRef);
|
||||
}
|
||||
inputStream = new BufferedInputStream(zipFile.getInputStream(entry));
|
||||
return decoder.decode(entry.getSize(), inputStream);
|
||||
result = decoder.decode(entry.getSize(), inputStream);
|
||||
} catch (Exception e) {
|
||||
throw new JadxException("Error decode: " + zipRef.getEntryName(), e);
|
||||
} finally {
|
||||
@@ -68,25 +73,20 @@ public final class ResourcesLoader {
|
||||
if (zipFile != null) {
|
||||
zipFile.close();
|
||||
}
|
||||
if (inputStream != null) {
|
||||
inputStream.close();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.debug("Error close zip file: {}", zipRef, e);
|
||||
LOG.error("Error close zip file: {}", zipRef, e);
|
||||
}
|
||||
close(inputStream);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static CodeWriter loadContent(final JadxDecompiler jadxRef, final ResourceFile rf) {
|
||||
static ResContainer loadContent(final JadxDecompiler jadxRef, final ResourceFile rf) {
|
||||
try {
|
||||
return (CodeWriter) decodeStream(rf, new ResourceDecoder() {
|
||||
return decodeStream(rf, new ResourceDecoder() {
|
||||
@Override
|
||||
public Object decode(long size, InputStream is) throws IOException {
|
||||
if (size > LOAD_SIZE_LIMIT) {
|
||||
return new CodeWriter().add("File too big, size: "
|
||||
+ String.format("%.2f KB", size / 1024.));
|
||||
}
|
||||
return loadContent(jadxRef, rf.getType(), is);
|
||||
public ResContainer decode(long size, InputStream is) throws IOException {
|
||||
return loadContent(jadxRef, rf, is, size);
|
||||
}
|
||||
});
|
||||
} catch (JadxException e) {
|
||||
@@ -94,21 +94,29 @@ public final class ResourcesLoader {
|
||||
CodeWriter cw = new CodeWriter();
|
||||
cw.add("Error decode ").add(rf.getType().toString().toLowerCase());
|
||||
cw.startLine(Utils.getStackTrace(e.getCause()));
|
||||
return cw;
|
||||
return ResContainer.singleFile(rf.getName(), cw);
|
||||
}
|
||||
}
|
||||
|
||||
private static CodeWriter loadContent(JadxDecompiler jadxRef, ResourceType type,
|
||||
InputStream inputStream) throws IOException {
|
||||
switch (type) {
|
||||
private static ResContainer loadContent(JadxDecompiler jadxRef, ResourceFile rf,
|
||||
InputStream inputStream, long size) throws IOException {
|
||||
switch (rf.getType()) {
|
||||
case MANIFEST:
|
||||
case XML:
|
||||
return jadxRef.getXmlParser().parse(inputStream);
|
||||
return ResContainer.singleFile(rf.getName(),
|
||||
jadxRef.getXmlParser().parse(inputStream));
|
||||
|
||||
case ARSC:
|
||||
return new ResTableParser().decodeToCodeWriter(inputStream);
|
||||
return new ResTableParser().decodeFiles(inputStream);
|
||||
|
||||
case IMG:
|
||||
return ResContainer.singleImageFile(rf.getName(), inputStream);
|
||||
}
|
||||
return loadToCodeWriter(inputStream);
|
||||
if (size > LOAD_SIZE_LIMIT) {
|
||||
return ResContainer.singleFile(rf.getName(),
|
||||
new CodeWriter().add("File too big, size: " + String.format("%.2f KB", size / 1024.)));
|
||||
}
|
||||
return ResContainer.singleFile(rf.getName(), loadToCodeWriter(inputStream));
|
||||
}
|
||||
|
||||
private void loadFile(List<ResourceFile> list, File file) {
|
||||
@@ -145,24 +153,12 @@ public final class ResourcesLoader {
|
||||
ResourceFile rf = new ResourceFile(jadxRef, name, type);
|
||||
rf.setZipRef(new ZipRef(zipFile, name));
|
||||
list.add(rf);
|
||||
// LOG.debug("Add resource entry: {}, size: {}", name, entry.getSize());
|
||||
}
|
||||
|
||||
private static CodeWriter loadToCodeWriter(InputStream is) throws IOException {
|
||||
public static CodeWriter loadToCodeWriter(InputStream is) throws IOException {
|
||||
CodeWriter cw = new CodeWriter();
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(READ_BUFFER_SIZE);
|
||||
byte[] buffer = new byte[READ_BUFFER_SIZE];
|
||||
int count;
|
||||
try {
|
||||
while ((count = is.read(buffer)) != -1) {
|
||||
baos.write(buffer, 0, count);
|
||||
}
|
||||
} finally {
|
||||
try {
|
||||
is.close();
|
||||
} catch (Exception ignore) {
|
||||
}
|
||||
}
|
||||
copyStream(is, baos);
|
||||
cw.add(baos.toString("UTF-8"));
|
||||
return cw;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import jadx.core.dex.visitors.DebugInfoVisitor;
|
||||
import jadx.core.dex.visitors.DependencyCollector;
|
||||
import jadx.core.dex.visitors.DotGraphVisitor;
|
||||
import jadx.core.dex.visitors.EnumVisitor;
|
||||
import jadx.core.dex.visitors.ExtractFieldInit;
|
||||
import jadx.core.dex.visitors.FallbackModeVisitor;
|
||||
import jadx.core.dex.visitors.IDexTreeVisitor;
|
||||
import jadx.core.dex.visitors.MethodInlineVisitor;
|
||||
@@ -31,7 +32,6 @@ import jadx.core.dex.visitors.ssa.EliminatePhiNodes;
|
||||
import jadx.core.dex.visitors.ssa.SSATransform;
|
||||
import jadx.core.dex.visitors.typeinference.FinishTypeInference;
|
||||
import jadx.core.dex.visitors.typeinference.TypeInference;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URL;
|
||||
@@ -50,9 +50,6 @@ public class Jadx {
|
||||
if (Consts.DEBUG) {
|
||||
LOG.info("debug enabled");
|
||||
}
|
||||
if (Jadx.class.desiredAssertionStatus()) {
|
||||
LOG.info("assertions enabled");
|
||||
}
|
||||
}
|
||||
|
||||
public static List<IDexTreeVisitor> getPassesList(IJadxArgs args, File outDir) {
|
||||
@@ -100,6 +97,7 @@ public class Jadx {
|
||||
}
|
||||
|
||||
passes.add(new MethodInlineVisitor());
|
||||
passes.add(new ExtractFieldInit());
|
||||
passes.add(new ClassModifier());
|
||||
passes.add(new EnumVisitor());
|
||||
passes.add(new PrepareForCodeGen());
|
||||
@@ -115,7 +113,7 @@ public class Jadx {
|
||||
|
||||
public static String getVersion() {
|
||||
try {
|
||||
ClassLoader classLoader = Utils.class.getClassLoader();
|
||||
ClassLoader classLoader = Jadx.class.getClassLoader();
|
||||
if (classLoader != null) {
|
||||
Enumeration<URL> resources = classLoader.getResources("META-INF/MANIFEST.MF");
|
||||
while (resources.hasMoreElements()) {
|
||||
|
||||
@@ -27,6 +27,8 @@ import java.util.zip.ZipOutputStream;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import static jadx.core.utils.files.FileUtils.close;
|
||||
|
||||
/**
|
||||
* Classes list for import into classpath graph
|
||||
*/
|
||||
@@ -115,13 +117,13 @@ public class ClsSet {
|
||||
out.putNextEntry(new ZipEntry(CLST_PKG_PATH + "/" + CLST_FILENAME));
|
||||
save(out);
|
||||
} finally {
|
||||
out.close();
|
||||
close(out);
|
||||
}
|
||||
} else {
|
||||
throw new JadxRuntimeException("Unknown file format: " + outputName);
|
||||
}
|
||||
} finally {
|
||||
outputStream.close();
|
||||
close(outputStream);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,7 +146,7 @@ public class ClsSet {
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
out.close();
|
||||
close(out);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,7 +158,7 @@ public class ClsSet {
|
||||
try {
|
||||
load(input);
|
||||
} finally {
|
||||
input.close();
|
||||
close(input);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,13 +179,13 @@ public class ClsSet {
|
||||
entry = in.getNextEntry();
|
||||
}
|
||||
} finally {
|
||||
in.close();
|
||||
close(in);
|
||||
}
|
||||
} else {
|
||||
throw new JadxRuntimeException("Unknown file format: " + name);
|
||||
}
|
||||
} finally {
|
||||
inputStream.close();
|
||||
close(inputStream);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -213,7 +215,7 @@ public class ClsSet {
|
||||
classes[i].setParents(parents);
|
||||
}
|
||||
} finally {
|
||||
in.close();
|
||||
close(in);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import jadx.core.utils.exceptions.DecodeException;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
@@ -25,6 +26,8 @@ public class ClspGraph {
|
||||
private final Map<String, Set<String>> ancestorCache = new WeakHashMap<String, Set<String>>();
|
||||
private Map<String, NClass> nameMap;
|
||||
|
||||
private final Set<String> missingClasses = new HashSet<String>();
|
||||
|
||||
public void load() throws IOException, DecodeException {
|
||||
ClsSet set = new ClsSet();
|
||||
set.load();
|
||||
@@ -73,7 +76,7 @@ public class ClspGraph {
|
||||
}
|
||||
NClass cls = nameMap.get(implClsName);
|
||||
if (cls == null) {
|
||||
LOG.debug("Missing class: {}", implClsName);
|
||||
missingClasses.add(clsName);
|
||||
return null;
|
||||
}
|
||||
if (isImplements(clsName, implClsName)) {
|
||||
@@ -104,7 +107,7 @@ public class ClspGraph {
|
||||
}
|
||||
NClass cls = nameMap.get(clsName);
|
||||
if (cls == null) {
|
||||
LOG.debug("Missing class: {}", clsName);
|
||||
missingClasses.add(clsName);
|
||||
return Collections.emptySet();
|
||||
}
|
||||
result = new HashSet<String>();
|
||||
@@ -122,4 +125,19 @@ public class ClspGraph {
|
||||
addAncestorsNames(p, result);
|
||||
}
|
||||
}
|
||||
|
||||
public void printMissingClasses() {
|
||||
int count = missingClasses.size();
|
||||
if (count == 0) {
|
||||
return;
|
||||
}
|
||||
LOG.warn("Found {} references to unknown classes", count);
|
||||
if (LOG.isDebugEnabled()) {
|
||||
List<String> clsNames = new ArrayList<String>(missingClasses);
|
||||
Collections.sort(clsNames);
|
||||
for (String cls : clsNames) {
|
||||
LOG.debug(" {}", cls);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package jadx.core.clsp;
|
||||
|
||||
import jadx.api.DefaultJadxArgs;
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.exceptions.DecodeException;
|
||||
import jadx.core.utils.files.InputFile;
|
||||
@@ -36,14 +36,14 @@ public class ConvertToClsSet {
|
||||
if (f.isDirectory()) {
|
||||
addFilesFromDirectory(f, inputFiles);
|
||||
} else {
|
||||
inputFiles.add(new InputFile(f));
|
||||
InputFile.addFilesFrom(f, inputFiles);
|
||||
}
|
||||
}
|
||||
for (InputFile inputFile : inputFiles) {
|
||||
LOG.info("Loaded: {}", inputFile.getFile());
|
||||
}
|
||||
|
||||
RootNode root = new RootNode(new DefaultJadxArgs());
|
||||
RootNode root = new RootNode(new JadxArgs());
|
||||
root.load(inputFiles);
|
||||
|
||||
ClsSet set = new ClsSet();
|
||||
@@ -53,8 +53,7 @@ public class ConvertToClsSet {
|
||||
LOG.info("done");
|
||||
}
|
||||
|
||||
private static void addFilesFromDirectory(File dir,
|
||||
List<InputFile> inputFiles) throws IOException, DecodeException {
|
||||
private static void addFilesFromDirectory(File dir, List<InputFile> inputFiles) {
|
||||
File[] files = dir.listFiles();
|
||||
if (files == null) {
|
||||
return;
|
||||
@@ -62,14 +61,13 @@ public class ConvertToClsSet {
|
||||
for (File file : files) {
|
||||
if (file.isDirectory()) {
|
||||
addFilesFromDirectory(file, inputFiles);
|
||||
}
|
||||
String fileName = file.getName();
|
||||
if (fileName.endsWith(".dex")
|
||||
|| fileName.endsWith(".jar")
|
||||
|| fileName.endsWith(".apk")) {
|
||||
inputFiles.add(new InputFile(file));
|
||||
} else {
|
||||
try {
|
||||
InputFile.addFilesFrom(file, inputFiles);
|
||||
} catch (Exception e) {
|
||||
LOG.warn("Skip file: {}, load error: {}", file, e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -130,11 +130,11 @@ public class AnnotationGen {
|
||||
return;
|
||||
}
|
||||
if (val instanceof String) {
|
||||
code.add(StringUtils.unescapeString((String) val));
|
||||
code.add(getStringUtils().unescapeString((String) val));
|
||||
} else if (val instanceof Integer) {
|
||||
code.add(TypeGen.formatInteger((Integer) val));
|
||||
} else if (val instanceof Character) {
|
||||
code.add(StringUtils.unescapeChar((Character) val));
|
||||
code.add(getStringUtils().unescapeChar((Character) val));
|
||||
} else if (val instanceof Boolean) {
|
||||
code.add(Boolean.TRUE.equals(val) ? "true" : "false");
|
||||
} else if (val instanceof Float) {
|
||||
@@ -172,4 +172,8 @@ public class AnnotationGen {
|
||||
throw new JadxRuntimeException("Can't decode value: " + val + " (" + val.getClass() + ")");
|
||||
}
|
||||
}
|
||||
|
||||
private StringUtils getStringUtils() {
|
||||
return cls.dex().root().getStringUtils();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,8 +15,10 @@ import jadx.core.dex.instructions.mods.ConstructorInsn;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.DexNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.parser.FieldValueAttr;
|
||||
import jadx.core.dex.nodes.parser.FieldInitAttr;
|
||||
import jadx.core.dex.nodes.parser.FieldInitAttr.InitType;
|
||||
import jadx.core.utils.ErrorsCounter;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.CodegenException;
|
||||
@@ -146,6 +148,7 @@ public class ClassGen {
|
||||
} else {
|
||||
clsCode.add("class ");
|
||||
}
|
||||
clsCode.attachDefinition(cls);
|
||||
clsCode.add(cls.getShortName());
|
||||
|
||||
addGenericMap(clsCode, cls.getGenericMap());
|
||||
@@ -177,7 +180,6 @@ public class ClassGen {
|
||||
clsCode.add(' ');
|
||||
}
|
||||
}
|
||||
clsCode.attachDefinition(cls);
|
||||
}
|
||||
|
||||
public boolean addGenericMap(CodeWriter code, Map<ArgType, List<ArgType>> gmap) {
|
||||
@@ -262,8 +264,10 @@ public class ClassGen {
|
||||
try {
|
||||
addMethod(code, mth);
|
||||
} catch (Exception e) {
|
||||
String msg = ErrorsCounter.methodError(mth, "Method generation error", e);
|
||||
code.startLine("/* " + msg + CodeWriter.NL + Utils.getStackTrace(e) + " */");
|
||||
code.newLine().add("/*");
|
||||
code.newLine().add(ErrorsCounter.methodError(mth, "Method generation error", e));
|
||||
code.newLine().add(Utils.getStackTrace(e));
|
||||
code.newLine().add("*/");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -338,18 +342,23 @@ public class ClassGen {
|
||||
code.startLine(f.getAccessFlags().makeString());
|
||||
useType(code, f.getType());
|
||||
code.add(' ');
|
||||
code.attachDefinition(f);
|
||||
code.add(f.getAlias());
|
||||
FieldValueAttr fv = f.get(AType.FIELD_VALUE);
|
||||
FieldInitAttr fv = f.get(AType.FIELD_INIT);
|
||||
if (fv != null) {
|
||||
code.add(" = ");
|
||||
if (fv.getValue() == null) {
|
||||
code.add(TypeGen.literalToString(0, f.getType()));
|
||||
code.add(TypeGen.literalToString(0, f.getType(), cls));
|
||||
} else {
|
||||
annotationGen.encodeValue(code, fv.getValue());
|
||||
if (fv.getValueType() == InitType.CONST) {
|
||||
annotationGen.encodeValue(code, fv.getValue());
|
||||
} else if (fv.getValueType() == InitType.INSN) {
|
||||
InsnGen insnGen = makeInsnGen(fv.getInsnMth());
|
||||
addInsnBody(insnGen, code, fv.getInsn());
|
||||
}
|
||||
}
|
||||
}
|
||||
code.add(';');
|
||||
code.attachDefinition(f);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -374,8 +383,7 @@ public class ClassGen {
|
||||
ConstructorInsn constrInsn = f.getConstrInsn();
|
||||
if (constrInsn.getArgsCount() > f.getStartArg()) {
|
||||
if (igen == null) {
|
||||
MethodGen mthGen = new MethodGen(this, enumFields.getStaticMethod());
|
||||
igen = new InsnGen(mthGen, false);
|
||||
igen = makeInsnGen(enumFields.getStaticMethod());
|
||||
}
|
||||
MethodNode callMth = cls.dex().resolveMethod(constrInsn.getCallMth());
|
||||
igen.generateMethodArguments(code, constrInsn, f.getStartArg(), callMth);
|
||||
@@ -399,6 +407,19 @@ public class ClassGen {
|
||||
}
|
||||
}
|
||||
|
||||
private InsnGen makeInsnGen(MethodNode mth) {
|
||||
MethodGen mthGen = new MethodGen(this, mth);
|
||||
return new InsnGen(mthGen, false);
|
||||
}
|
||||
|
||||
private void addInsnBody(InsnGen insnGen, CodeWriter code, InsnNode insn) {
|
||||
try {
|
||||
insnGen.makeInsn(insn, code, InsnGen.Flags.BODY_ONLY_NOWRAP);
|
||||
} catch (Exception e) {
|
||||
ErrorsCounter.classError(cls, "Failed to generate init code", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void useType(CodeWriter code, ArgType type) {
|
||||
PrimitiveType stype = type.getPrimitiveType();
|
||||
if (stype == null) {
|
||||
@@ -480,6 +501,10 @@ public class ClassGen {
|
||||
if (searchCollision(cls.dex(), useCls, extClsInfo)) {
|
||||
return fullName;
|
||||
}
|
||||
// ignore classes from default package
|
||||
if (extClsInfo.isDefaultPackage()) {
|
||||
return shortName;
|
||||
}
|
||||
if (extClsInfo.getPackage().equals(useCls.getPackage())) {
|
||||
fullName = extClsInfo.getNameWithoutPackage();
|
||||
}
|
||||
@@ -561,7 +586,8 @@ public class ClassGen {
|
||||
|
||||
private void insertRenameInfo(CodeWriter code, ClassNode cls) {
|
||||
ClassInfo classInfo = cls.getClassInfo();
|
||||
if (classInfo.isRenamed()) {
|
||||
if (classInfo.isRenamed()
|
||||
&& !cls.getShortName().equals(cls.getAlias().getShortName())) {
|
||||
code.startLine("/* renamed from: ").add(classInfo.getFullName()).add(" */");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,12 +12,14 @@ import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import static jadx.core.utils.files.FileUtils.close;
|
||||
|
||||
public class CodeWriter {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(CodeWriter.class);
|
||||
private static final int MAX_FILENAME_LENGTH = 128;
|
||||
|
||||
public static final String NL = System.getProperty("line.separator");
|
||||
public static final String INDENT = " ";
|
||||
@@ -33,7 +35,9 @@ public class CodeWriter {
|
||||
INDENT + INDENT + INDENT + INDENT + INDENT,
|
||||
};
|
||||
|
||||
private final StringBuilder buf = new StringBuilder();
|
||||
private StringBuilder buf = new StringBuilder();
|
||||
@Nullable
|
||||
private String code;
|
||||
private String indentStr;
|
||||
private int indent;
|
||||
|
||||
@@ -113,7 +117,7 @@ public class CodeWriter {
|
||||
}
|
||||
line += code.line;
|
||||
offset = code.offset;
|
||||
buf.append(code);
|
||||
buf.append(code.buf);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -194,12 +198,13 @@ public class CodeWriter {
|
||||
}
|
||||
}
|
||||
|
||||
public Object attachDefinition(LineAttrNode obj) {
|
||||
return attachAnnotation(new DefinitionWrapper(obj), new CodePosition(line, offset));
|
||||
public void attachDefinition(LineAttrNode obj) {
|
||||
attachAnnotation(obj);
|
||||
attachAnnotation(new DefinitionWrapper(obj), new CodePosition(line, offset));
|
||||
}
|
||||
|
||||
public Object attachAnnotation(Object obj) {
|
||||
return attachAnnotation(obj, new CodePosition(line, offset + 1));
|
||||
public void attachAnnotation(Object obj) {
|
||||
attachAnnotation(obj, new CodePosition(line, offset + 1));
|
||||
}
|
||||
|
||||
private Object attachAnnotation(Object obj, CodePosition pos) {
|
||||
@@ -232,7 +237,11 @@ public class CodeWriter {
|
||||
}
|
||||
|
||||
public void finish() {
|
||||
removeFirstEmptyLine();
|
||||
buf.trimToSize();
|
||||
code = buf.toString();
|
||||
buf = null;
|
||||
|
||||
Iterator<Map.Entry<CodePosition, Object>> it = annotations.entrySet().iterator();
|
||||
while (it.hasNext()) {
|
||||
Map.Entry<CodePosition, Object> entry = it.next();
|
||||
@@ -245,28 +254,23 @@ public class CodeWriter {
|
||||
}
|
||||
}
|
||||
|
||||
private static String removeFirstEmptyLine(String str) {
|
||||
if (str.startsWith(NL)) {
|
||||
return str.substring(NL.length());
|
||||
private void removeFirstEmptyLine() {
|
||||
if (buf.indexOf(NL) == 0) {
|
||||
buf.delete(0, NL.length());
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
public int length() {
|
||||
public int bufLength() {
|
||||
return buf.length();
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return buf.length() == 0;
|
||||
}
|
||||
|
||||
public boolean notEmpty() {
|
||||
return buf.length() != 0;
|
||||
public String getCodeStr() {
|
||||
return code;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return buf.toString();
|
||||
return buf == null ? code : buf.toString();
|
||||
}
|
||||
|
||||
public void save(File dir, String subDir, String fileName) {
|
||||
@@ -278,48 +282,19 @@ public class CodeWriter {
|
||||
}
|
||||
|
||||
public void save(File file) {
|
||||
String name = file.getName();
|
||||
if (name.length() > MAX_FILENAME_LENGTH) {
|
||||
int dotIndex = name.indexOf('.');
|
||||
int cutAt = MAX_FILENAME_LENGTH - name.length() + dotIndex - 1;
|
||||
if (cutAt <= 0) {
|
||||
name = name.substring(0, MAX_FILENAME_LENGTH - 1);
|
||||
} else {
|
||||
name = name.substring(0, cutAt) + name.substring(dotIndex);
|
||||
}
|
||||
file = new File(file.getParentFile(), name);
|
||||
if (code == null) {
|
||||
finish();
|
||||
}
|
||||
|
||||
File outFile = FileUtils.prepareFile(file);
|
||||
PrintWriter out = null;
|
||||
try {
|
||||
FileUtils.makeDirsForFile(file);
|
||||
out = new PrintWriter(file, "UTF-8");
|
||||
String code = buf.toString();
|
||||
code = removeFirstEmptyLine(code);
|
||||
out = new PrintWriter(outFile, "UTF-8");
|
||||
out.println(code);
|
||||
} catch (Exception e) {
|
||||
LOG.error("Save file error", e);
|
||||
} finally {
|
||||
if (out != null) {
|
||||
out.close();
|
||||
}
|
||||
close(out);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return buf.toString().hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (!(o instanceof CodeWriter)) {
|
||||
return false;
|
||||
}
|
||||
CodeWriter that = (CodeWriter) o;
|
||||
return buf.toString().equals(that.buf.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,24 +36,24 @@ import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.ErrorsCounter;
|
||||
import jadx.core.utils.RegionUtils;
|
||||
import jadx.core.utils.StringUtils;
|
||||
import jadx.core.utils.exceptions.CodegenException;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.EnumSet;
|
||||
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;
|
||||
|
||||
import static jadx.core.utils.android.AndroidResourcesUtils.handleAppResField;
|
||||
|
||||
public class InsnGen {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(InsnGen.class);
|
||||
|
||||
@@ -80,9 +80,9 @@ public class InsnGen {
|
||||
}
|
||||
|
||||
public void addArgDot(CodeWriter code, InsnArg arg) throws CodegenException {
|
||||
int len = code.length();
|
||||
int len = code.bufLength();
|
||||
addArg(code, arg, true);
|
||||
if (len != code.length()) {
|
||||
if (len != code.bufLength()) {
|
||||
code.add('.');
|
||||
}
|
||||
}
|
||||
@@ -123,13 +123,16 @@ public class InsnGen {
|
||||
}
|
||||
|
||||
public void declareVar(CodeWriter code, RegisterArg arg) {
|
||||
if (arg.getSVar().contains(AFlag.FINAL)) {
|
||||
code.add("final ");
|
||||
}
|
||||
useType(code, arg.getType());
|
||||
code.add(' ');
|
||||
code.add(mgen.getNameGen().assignArg(arg));
|
||||
}
|
||||
|
||||
private static String lit(LiteralArg arg) {
|
||||
return TypeGen.literalToString(arg.getLiteral(), arg.getType());
|
||||
private String lit(LiteralArg arg) {
|
||||
return TypeGen.literalToString(arg.getLiteral(), arg.getType(), mth);
|
||||
}
|
||||
|
||||
private void instanceField(CodeWriter code, FieldInfo field, InsnArg arg) throws CodegenException {
|
||||
@@ -144,16 +147,19 @@ public class InsnGen {
|
||||
if (fieldNode != null) {
|
||||
FieldReplaceAttr replace = fieldNode.get(AType.FIELD_REPLACE);
|
||||
if (replace != null) {
|
||||
FieldInfo info = replace.getFieldInfo();
|
||||
if (replace.isOuterClass()) {
|
||||
useClass(code, info.getDeclClass());
|
||||
code.add(".this");
|
||||
switch (replace.getReplaceType()) {
|
||||
case CLASS_INSTANCE:
|
||||
useClass(code, replace.getClsRef());
|
||||
code.add(".this");
|
||||
break;
|
||||
case VAR:
|
||||
addArg(code, replace.getVarRef());
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
addArgDot(code, arg);
|
||||
fieldNode = mth.dex().resolveField(field);
|
||||
if (fieldNode != null) {
|
||||
code.attachAnnotation(fieldNode);
|
||||
}
|
||||
@@ -165,12 +171,7 @@ public class InsnGen {
|
||||
boolean fieldFromThisClass = clsGen.getClassNode().getClassInfo().equals(declClass);
|
||||
if (!fieldFromThisClass) {
|
||||
// Android specific resources class handler
|
||||
ClassInfo parentClass = declClass.getParentClass();
|
||||
if (parentClass != null && parentClass.getShortName().equals("R")) {
|
||||
clsGen.useClass(code, parentClass);
|
||||
code.add('.');
|
||||
code.add(declClass.getAlias().getShortName());
|
||||
} else {
|
||||
if (!handleAppResField(code, clsGen, declClass)) {
|
||||
clsGen.useClass(code, declClass);
|
||||
}
|
||||
code.add('.');
|
||||
@@ -231,7 +232,7 @@ public class InsnGen {
|
||||
switch (insn.getType()) {
|
||||
case CONST_STR:
|
||||
String str = ((ConstStringNode) insn).getString();
|
||||
code.add(StringUtils.unescapeString(str));
|
||||
code.add(mth.dex().root().getStringUtils().unescapeString(str));
|
||||
break;
|
||||
|
||||
case CONST_CLASS:
|
||||
@@ -445,7 +446,7 @@ public class InsnGen {
|
||||
|
||||
/* fallback mode instructions */
|
||||
case IF:
|
||||
assert isFallback() : "if insn in not fallback mode";
|
||||
fallbackOnlyInsn(insn);
|
||||
IfNode ifInsn = (IfNode) insn;
|
||||
code.add("if (");
|
||||
addArg(code, insn.getArg(0));
|
||||
@@ -456,17 +457,17 @@ public class InsnGen {
|
||||
break;
|
||||
|
||||
case GOTO:
|
||||
assert isFallback();
|
||||
fallbackOnlyInsn(insn);
|
||||
code.add("goto ").add(MethodGen.getLabelName(((GotoNode) insn).getTarget()));
|
||||
break;
|
||||
|
||||
case MOVE_EXCEPTION:
|
||||
assert isFallback();
|
||||
fallbackOnlyInsn(insn);
|
||||
code.add("move-exception");
|
||||
break;
|
||||
|
||||
case SWITCH:
|
||||
assert isFallback();
|
||||
fallbackOnlyInsn(insn);
|
||||
SwitchNode sw = (SwitchNode) insn;
|
||||
code.add("switch(");
|
||||
addArg(code, insn.getArg(0));
|
||||
@@ -484,7 +485,7 @@ public class InsnGen {
|
||||
break;
|
||||
|
||||
case FILL_ARRAY:
|
||||
assert isFallback();
|
||||
fallbackOnlyInsn(insn);
|
||||
FillArrayNode arrayNode = (FillArrayNode) insn;
|
||||
Object data = arrayNode.getData();
|
||||
String arrStr;
|
||||
@@ -504,8 +505,19 @@ public class InsnGen {
|
||||
|
||||
case NEW_INSTANCE:
|
||||
// only fallback - make new instance in constructor invoke
|
||||
assert isFallback();
|
||||
code.add("new " + insn.getResult().getType());
|
||||
fallbackOnlyInsn(insn);
|
||||
code.add("new ").add(insn.getResult().getType().toString());
|
||||
break;
|
||||
|
||||
case PHI:
|
||||
case MERGE:
|
||||
fallbackOnlyInsn(insn);
|
||||
code.add(insn.getType().toString()).add("(");
|
||||
for (InsnArg insnArg : insn.getArguments()) {
|
||||
addArg(code, insnArg);
|
||||
code.add(' ');
|
||||
}
|
||||
code.add(")");
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -513,6 +525,12 @@ public class InsnGen {
|
||||
}
|
||||
}
|
||||
|
||||
private void fallbackOnlyInsn(InsnNode insn) throws CodegenException {
|
||||
if (!fallback) {
|
||||
throw new CodegenException(insn.getType() + " can be used only in fallback mode");
|
||||
}
|
||||
}
|
||||
|
||||
private void filledNewArray(FilledNewArrayNode insn, CodeWriter code) throws CodegenException {
|
||||
code.add("new ");
|
||||
useType(code, insn.getArrayType());
|
||||
@@ -531,30 +549,7 @@ public class InsnGen {
|
||||
throws CodegenException {
|
||||
ClassNode cls = mth.dex().resolveClass(insn.getClassType());
|
||||
if (cls != null && cls.contains(AFlag.ANONYMOUS_CLASS) && !fallback) {
|
||||
// anonymous class construction
|
||||
ArgType parent;
|
||||
if (cls.getInterfaces().size() == 1) {
|
||||
parent = cls.getInterfaces().get(0);
|
||||
} else {
|
||||
parent = cls.getSuperClass();
|
||||
}
|
||||
cls.add(AFlag.DONT_GENERATE);
|
||||
MethodNode defCtr = cls.getDefaultConstructor();
|
||||
if (defCtr != null) {
|
||||
if (RegionUtils.notEmpty(defCtr.getRegion())) {
|
||||
defCtr.add(AFlag.ANONYMOUS_CONSTRUCTOR);
|
||||
} else {
|
||||
defCtr.add(AFlag.DONT_GENERATE);
|
||||
}
|
||||
}
|
||||
code.add("new ");
|
||||
if (parent == null) {
|
||||
code.add("Object");
|
||||
} else {
|
||||
useClass(code, parent);
|
||||
}
|
||||
code.add("() ");
|
||||
new ClassGen(cls, mgen.getClassGen().getParentGen()).addClassBody(code);
|
||||
inlineAnonymousConstr(code, cls, insn);
|
||||
return;
|
||||
}
|
||||
if (insn.isSelf()) {
|
||||
@@ -568,7 +563,42 @@ public class InsnGen {
|
||||
code.add("new ");
|
||||
useClass(code, insn.getClassType());
|
||||
}
|
||||
generateMethodArguments(code, insn, 0, mth.dex().resolveMethod(insn.getCallMth()));
|
||||
MethodNode callMth = mth.dex().resolveMethod(insn.getCallMth());
|
||||
generateMethodArguments(code, insn, 0, callMth);
|
||||
}
|
||||
|
||||
private void inlineAnonymousConstr(CodeWriter code, ClassNode cls, ConstructorInsn insn) throws CodegenException {
|
||||
// anonymous class construction
|
||||
if (cls.contains(AFlag.DONT_GENERATE)) {
|
||||
code.add("/* anonymous class already generated */");
|
||||
ErrorsCounter.methodError(mth, "Anonymous class already generated: " + cls);
|
||||
return;
|
||||
}
|
||||
ArgType parent;
|
||||
if (cls.getInterfaces().size() == 1) {
|
||||
parent = cls.getInterfaces().get(0);
|
||||
} else {
|
||||
parent = cls.getSuperClass();
|
||||
}
|
||||
cls.add(AFlag.DONT_GENERATE);
|
||||
MethodNode defCtr = cls.getDefaultConstructor();
|
||||
if (defCtr != null) {
|
||||
if (RegionUtils.notEmpty(defCtr.getRegion())) {
|
||||
defCtr.add(AFlag.ANONYMOUS_CONSTRUCTOR);
|
||||
} else {
|
||||
defCtr.add(AFlag.DONT_GENERATE);
|
||||
}
|
||||
}
|
||||
code.add("new ");
|
||||
if (parent == null) {
|
||||
code.add("Object");
|
||||
} else {
|
||||
useClass(code, parent);
|
||||
}
|
||||
MethodNode callMth = mth.dex().resolveMethod(insn.getCallMth());
|
||||
generateMethodArguments(code, insn, 0, callMth);
|
||||
code.add(' ');
|
||||
new ClassGen(cls, mgen.getClassGen().getParentGen()).addClassBody(code);
|
||||
}
|
||||
|
||||
private void makeInvoke(InvokeNode insn, CodeWriter code) throws CodegenException {
|
||||
@@ -627,23 +657,43 @@ public class InsnGen {
|
||||
}
|
||||
int argsCount = insn.getArgsCount();
|
||||
code.add('(');
|
||||
boolean firstArg = true;
|
||||
if (k < argsCount) {
|
||||
boolean overloaded = callMth != null && callMth.isArgsOverload();
|
||||
for (int i = k; i < argsCount; i++) {
|
||||
InsnArg arg = insn.getArg(i);
|
||||
if (arg.contains(AFlag.SKIP_ARG)) {
|
||||
continue;
|
||||
}
|
||||
RegisterArg callArg = getCallMthArg(callMth, i - startArgNum);
|
||||
if (callArg != null && callArg.contains(AFlag.SKIP_ARG)) {
|
||||
continue;
|
||||
}
|
||||
if (!firstArg) {
|
||||
code.add(", ");
|
||||
}
|
||||
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(", ");
|
||||
}
|
||||
firstArg = false;
|
||||
}
|
||||
}
|
||||
code.add(')');
|
||||
}
|
||||
|
||||
private static RegisterArg getCallMthArg(@Nullable MethodNode callMth, int num) {
|
||||
if (callMth == null) {
|
||||
return null;
|
||||
}
|
||||
List<RegisterArg> args = callMth.getArguments(false);
|
||||
if (args != null && num < args.size()) {
|
||||
return args.get(num);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add additional cast for overloaded method argument.
|
||||
*/
|
||||
@@ -701,9 +751,9 @@ public class InsnGen {
|
||||
regs[callArg.getRegNum()] = arg;
|
||||
}
|
||||
// replace args
|
||||
InsnNode inlCopy = inl.copy();
|
||||
List<RegisterArg> inlArgs = new ArrayList<RegisterArg>();
|
||||
inl.getRegisterArgs(inlArgs);
|
||||
Map<RegisterArg, InsnArg> toRevert = new HashMap<RegisterArg, InsnArg>();
|
||||
inlCopy.getRegisterArgs(inlArgs);
|
||||
for (RegisterArg r : inlArgs) {
|
||||
int regNum = r.getRegNum();
|
||||
if (regNum >= regs.length) {
|
||||
@@ -713,16 +763,11 @@ public class InsnGen {
|
||||
if (repl == null) {
|
||||
LOG.warn("Not passed register {} in method call: {} from {}", r, callMthNode, mth);
|
||||
} else {
|
||||
inl.replaceArg(r, repl);
|
||||
toRevert.put(r, repl);
|
||||
inlCopy.replaceArg(r, repl);
|
||||
}
|
||||
}
|
||||
}
|
||||
makeInsn(inl, code, Flags.BODY_ONLY);
|
||||
// revert changes in 'MethodInlineAttr'
|
||||
for (Map.Entry<RegisterArg, InsnArg> e : toRevert.entrySet()) {
|
||||
inl.replaceArg(e.getValue(), e.getKey());
|
||||
}
|
||||
makeInsn(inlCopy, code, Flags.BODY_ONLY);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import jadx.core.dex.info.AccessInfo;
|
||||
import jadx.core.dex.instructions.InsnType;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.instructions.args.SSAVar;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.trycatch.CatchAttr;
|
||||
@@ -56,8 +57,8 @@ public class MethodGen {
|
||||
|
||||
public boolean addDefinition(CodeWriter code) {
|
||||
if (mth.getMethodInfo().isClassInit()) {
|
||||
code.startLine("static");
|
||||
code.attachDefinition(mth);
|
||||
code.startLine("static");
|
||||
return true;
|
||||
}
|
||||
if (mth.contains(AFlag.ANONYMOUS_CONSTRUCTOR)) {
|
||||
@@ -86,10 +87,12 @@ public class MethodGen {
|
||||
code.add(' ');
|
||||
}
|
||||
if (mth.getAccessFlags().isConstructor()) {
|
||||
code.attachDefinition(mth);
|
||||
code.add(classGen.getClassNode().getShortName()); // constructor
|
||||
} else {
|
||||
classGen.useType(code, mth.getReturnType());
|
||||
code.add(' ');
|
||||
code.attachDefinition(mth);
|
||||
code.add(mth.getAlias());
|
||||
}
|
||||
code.add('(');
|
||||
@@ -112,7 +115,6 @@ public class MethodGen {
|
||||
code.add(')');
|
||||
|
||||
annotationGen.addThrows(mth, code);
|
||||
code.attachDefinition(mth);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -126,6 +128,10 @@ public class MethodGen {
|
||||
if (paramsAnnotation != null) {
|
||||
annotationGen.addForParameter(argsCode, paramsAnnotation, i);
|
||||
}
|
||||
SSAVar argSVar = arg.getSVar();
|
||||
if (argSVar!= null && argSVar.contains(AFlag.FINAL)) {
|
||||
argsCode.add("final ");
|
||||
}
|
||||
if (!it.hasNext() && mth.getAccessFlags().isVarArgs()) {
|
||||
// change last array argument to varargs
|
||||
ArgType type = arg.getType();
|
||||
@@ -161,7 +167,7 @@ public class MethodGen {
|
||||
if (cause != null) {
|
||||
code.newLine();
|
||||
code.add("/*");
|
||||
code.startLine("Error: ").add(Utils.getStackTrace(cause));
|
||||
code.newLine().add("Error: ").add(Utils.getStackTrace(cause));
|
||||
code.add("*/");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ import jadx.core.dex.nodes.IBlock;
|
||||
import jadx.core.dex.nodes.IContainer;
|
||||
import jadx.core.dex.nodes.IRegion;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.parser.FieldValueAttr;
|
||||
import jadx.core.dex.nodes.parser.FieldInitAttr;
|
||||
import jadx.core.dex.regions.Region;
|
||||
import jadx.core.dex.regions.SwitchRegion;
|
||||
import jadx.core.dex.regions.SynchronizedRegion;
|
||||
@@ -249,13 +249,13 @@ public class RegionGen extends InsnGen {
|
||||
} else {
|
||||
staticField(code, fn.getFieldInfo());
|
||||
// print original value, sometimes replace with incorrect field
|
||||
FieldValueAttr valueAttr = fn.get(AType.FIELD_VALUE);
|
||||
FieldInitAttr valueAttr = fn.get(AType.FIELD_INIT);
|
||||
if (valueAttr != null && valueAttr.getValue() != null) {
|
||||
code.add(" /*").add(valueAttr.getValue().toString()).add("*/");
|
||||
}
|
||||
}
|
||||
} else if (k instanceof Integer) {
|
||||
code.add(TypeGen.literalToString((Integer) k, arg.getType()));
|
||||
code.add(TypeGen.literalToString((Integer) k, arg.getType(), mth));
|
||||
} else {
|
||||
throw new JadxRuntimeException("Unexpected key in switch: " + (k != null ? k.getClass() : null));
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package jadx.core.codegen;
|
||||
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.PrimitiveType;
|
||||
import jadx.core.dex.nodes.IDexNode;
|
||||
import jadx.core.utils.StringUtils;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
@@ -31,7 +33,16 @@ public class TypeGen {
|
||||
*
|
||||
* @throws JadxRuntimeException for incorrect type or literal value
|
||||
*/
|
||||
public static String literalToString(long lit, ArgType type, IDexNode dexNode) {
|
||||
return literalToString(lit, type, dexNode.root().getStringUtils());
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public static String literalToString(long lit, ArgType type) {
|
||||
return literalToString(lit, type, new StringUtils(new JadxArgs()));
|
||||
}
|
||||
|
||||
private static String literalToString(long lit, ArgType type, StringUtils stringUtils) {
|
||||
if (type == null || !type.isTypeKnown()) {
|
||||
String n = Long.toString(lit);
|
||||
if (Math.abs(lit) > 100) {
|
||||
@@ -46,7 +57,7 @@ public class TypeGen {
|
||||
case BOOLEAN:
|
||||
return lit == 0 ? "false" : "true";
|
||||
case CHAR:
|
||||
return StringUtils.unescapeChar((char) lit);
|
||||
return stringUtils.unescapeChar((char) lit);
|
||||
case BYTE:
|
||||
return formatByte((byte) lit);
|
||||
case SHORT:
|
||||
|
||||
@@ -6,13 +6,17 @@ import jadx.core.dex.attributes.nodes.SourceFileAttr;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.info.FieldInfo;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.DexNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
@@ -40,11 +44,16 @@ public class Deobfuscator {
|
||||
private final Map<FieldInfo, String> fldMap = new HashMap<FieldInfo, String>();
|
||||
private final Map<MethodInfo, String> mthMap = new HashMap<MethodInfo, String>();
|
||||
|
||||
private final Map<MethodInfo, OverridedMethodsNode> ovrdMap = new HashMap<MethodInfo, OverridedMethodsNode>();
|
||||
private final List<OverridedMethodsNode> ovrd = new ArrayList<OverridedMethodsNode>();
|
||||
|
||||
private final PackageNode rootPackage = new PackageNode("");
|
||||
private final Set<String> pkgSet = new TreeSet<String>();
|
||||
|
||||
private final int maxLength;
|
||||
private final int minLength;
|
||||
private final boolean useSourceNameAsAlias;
|
||||
|
||||
private int pkgIndex = 0;
|
||||
private int clsIndex = 0;
|
||||
private int fldIndex = 0;
|
||||
@@ -56,6 +65,7 @@ public class Deobfuscator {
|
||||
|
||||
this.minLength = args.getDeobfuscationMinLength();
|
||||
this.maxLength = args.getDeobfuscationMaxLength();
|
||||
this.useSourceNameAsAlias = args.useSourceNameAsClassAlias();
|
||||
|
||||
this.deobfPresets = new DeobfPresets(this, deobfMapFile);
|
||||
}
|
||||
@@ -95,6 +105,32 @@ public class Deobfuscator {
|
||||
processClass(dexNode, cls);
|
||||
}
|
||||
}
|
||||
postProcess();
|
||||
}
|
||||
|
||||
private void postProcess() {
|
||||
int id = 1;
|
||||
for (OverridedMethodsNode o : ovrd) {
|
||||
|
||||
Iterator<MethodInfo> it = o.getMethods().iterator();
|
||||
if (it.hasNext()) {
|
||||
MethodInfo mth = it.next();
|
||||
|
||||
if (mth.isRenamed() && !mth.isAliasFromPreset()) {
|
||||
mth.setAlias(String.format("mo%d%s", id, makeName(mth.getName())));
|
||||
}
|
||||
String firstMethodAlias = mth.getAlias();
|
||||
|
||||
while (it.hasNext()) {
|
||||
mth = it.next();
|
||||
if (!mth.getAlias().equals(firstMethodAlias)) {
|
||||
mth.setAlias(firstMethodAlias);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
id++;
|
||||
}
|
||||
}
|
||||
|
||||
void clear() {
|
||||
@@ -102,6 +138,98 @@ public class Deobfuscator {
|
||||
clsMap.clear();
|
||||
fldMap.clear();
|
||||
mthMap.clear();
|
||||
|
||||
ovrd.clear();
|
||||
ovrdMap.clear();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static ClassNode resolveOverridingInternal(DexNode dex, ClassNode cls, String signature,
|
||||
Set<MethodInfo> overrideSet, ClassNode rootClass) {
|
||||
ClassNode result = null;
|
||||
|
||||
for (MethodNode m : cls.getMethods()) {
|
||||
if (m.getMethodInfo().getShortId().startsWith(signature)) {
|
||||
result = cls;
|
||||
if (!overrideSet.contains(m.getMethodInfo())) {
|
||||
overrideSet.add(m.getMethodInfo());
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ArgType superClass = cls.getSuperClass();
|
||||
if (superClass != null) {
|
||||
ClassNode superNode = dex.resolveClass(superClass);
|
||||
if (superNode != null) {
|
||||
ClassNode clsWithMth = resolveOverridingInternal(dex, superNode, signature, overrideSet, rootClass);
|
||||
if (clsWithMth != null) {
|
||||
if ((result != null) && (result != cls)) {
|
||||
if (clsWithMth != result) {
|
||||
LOG.warn(String.format("Multiple overriding '%s' from '%s' and '%s' in '%s'",
|
||||
signature,
|
||||
result.getFullName(), clsWithMth.getFullName(),
|
||||
rootClass.getFullName()));
|
||||
}
|
||||
} else {
|
||||
result = clsWithMth;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (ArgType iFaceType : cls.getInterfaces()) {
|
||||
ClassNode iFaceNode = dex.resolveClass(iFaceType);
|
||||
if (iFaceNode != null) {
|
||||
ClassNode clsWithMth = resolveOverridingInternal(dex, iFaceNode, signature, overrideSet, rootClass);
|
||||
if (clsWithMth != null) {
|
||||
if ((result != null) && (result != cls)) {
|
||||
if (clsWithMth != result) {
|
||||
LOG.warn(String.format("Multiple overriding '%s' from '%s' and '%s' in '%s'",
|
||||
signature,
|
||||
result.getFullName(), clsWithMth.getFullName(),
|
||||
rootClass.getFullName()));
|
||||
}
|
||||
} else {
|
||||
result = clsWithMth;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void resolveOverriding(DexNode dex, ClassNode cls, MethodNode mth) {
|
||||
Set<MethodInfo> overrideSet = new HashSet<MethodInfo>();
|
||||
resolveOverridingInternal(dex, cls, mth.getMethodInfo().makeSignature(false), overrideSet, cls);
|
||||
|
||||
if (overrideSet.size() > 1) {
|
||||
OverridedMethodsNode overrideNode = null;
|
||||
for (MethodInfo _mth : overrideSet) {
|
||||
if (ovrdMap.containsKey(_mth)) {
|
||||
overrideNode = ovrdMap.get(_mth);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (overrideNode == null) {
|
||||
overrideNode = new OverridedMethodsNode(overrideSet);
|
||||
ovrd.add(overrideNode);
|
||||
}
|
||||
|
||||
for (MethodInfo _mth : overrideSet) {
|
||||
if (!ovrdMap.containsKey(_mth)) {
|
||||
ovrdMap.put(_mth, overrideNode);
|
||||
if (!overrideNode.contains(_mth)) {
|
||||
overrideNode.add(_mth);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
overrideSet.clear();
|
||||
overrideSet = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void processClass(DexNode dex, ClassNode cls) {
|
||||
@@ -123,6 +251,10 @@ public class Deobfuscator {
|
||||
if (alias != null) {
|
||||
methodInfo.setAlias(alias);
|
||||
}
|
||||
|
||||
if (mth.isVirtual()) {
|
||||
resolveOverriding(dex, cls, mth);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,7 +344,12 @@ public class Deobfuscator {
|
||||
|
||||
private String makeClsAlias(ClassNode cls) {
|
||||
ClassInfo classInfo = cls.getClassInfo();
|
||||
String alias = getAliasFromSourceFile(cls);
|
||||
String alias = null;
|
||||
|
||||
if (this.useSourceNameAsAlias) {
|
||||
alias = getAliasFromSourceFile(cls);
|
||||
}
|
||||
|
||||
if (alias == null) {
|
||||
String clsName = classInfo.getShortName();
|
||||
alias = String.format("C%04d%s", clsIndex++, makeName(clsName));
|
||||
@@ -269,6 +406,7 @@ public class Deobfuscator {
|
||||
alias = deobfPresets.getForMth(methodInfo);
|
||||
if (alias != null) {
|
||||
mthMap.put(methodInfo, alias);
|
||||
methodInfo.setAliasFromPreset(true);
|
||||
return alias;
|
||||
}
|
||||
if (shouldRename(mth.getName())) {
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package jadx.core.deobf;
|
||||
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/* package */ class OverridedMethodsNode {
|
||||
|
||||
private Set<MethodInfo> methods;
|
||||
|
||||
public OverridedMethodsNode(Set<MethodInfo> methodsSet) {
|
||||
methods = methodsSet;
|
||||
}
|
||||
|
||||
public boolean contains(MethodInfo mth) {
|
||||
return methods.contains(mth);
|
||||
}
|
||||
|
||||
public void add(MethodInfo mth) {
|
||||
methods.add(mth);
|
||||
}
|
||||
|
||||
public Set<MethodInfo> getMethods() {
|
||||
return methods;
|
||||
}
|
||||
}
|
||||
@@ -61,10 +61,15 @@ public class PackageNode {
|
||||
if (cachedPackageFullAlias == null) {
|
||||
Stack<PackageNode> pp = getParentPackages();
|
||||
StringBuilder result = new StringBuilder();
|
||||
result.append(pp.pop().getAlias());
|
||||
while (pp.size() > 0) {
|
||||
result.append(SEPARATOR_CHAR);
|
||||
|
||||
if (pp.size() > 0) {
|
||||
result.append(pp.pop().getAlias());
|
||||
while (pp.size() > 0) {
|
||||
result.append(SEPARATOR_CHAR);
|
||||
result.append(pp.pop().getAlias());
|
||||
}
|
||||
} else {
|
||||
result.append(this.getAlias());
|
||||
}
|
||||
cachedPackageFullAlias = result.toString();
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ public enum AFlag {
|
||||
LOOP_END,
|
||||
|
||||
SYNTHETIC,
|
||||
FINAL, // SSAVar attribute for make var final
|
||||
|
||||
RETURN, // block contains only return instruction
|
||||
ORIG_RETURN,
|
||||
@@ -22,6 +23,7 @@ public enum AFlag {
|
||||
REMOVE,
|
||||
|
||||
SKIP_FIRST_ARG,
|
||||
SKIP_ARG, // skip argument in invoke call
|
||||
ANONYMOUS_CONSTRUCTOR,
|
||||
ANONYMOUS_CLASS,
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package jadx.core.dex.attributes;
|
||||
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.EdgeInsnAttr;
|
||||
import jadx.core.dex.attributes.nodes.EnumClassAttr;
|
||||
import jadx.core.dex.attributes.nodes.EnumMapAttr;
|
||||
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
|
||||
@@ -15,7 +16,7 @@ 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;
|
||||
import jadx.core.dex.nodes.parser.FieldValueAttr;
|
||||
import jadx.core.dex.nodes.parser.FieldInitAttr;
|
||||
import jadx.core.dex.trycatch.CatchAttr;
|
||||
import jadx.core.dex.trycatch.ExcHandlerAttr;
|
||||
import jadx.core.dex.trycatch.SplitterBlockAttr;
|
||||
@@ -30,12 +31,13 @@ public class AType<T extends IAttribute> {
|
||||
|
||||
public static final AType<AttrList<JumpInfo>> JUMP = new AType<AttrList<JumpInfo>>();
|
||||
public static final AType<AttrList<LoopInfo>> LOOP = new AType<AttrList<LoopInfo>>();
|
||||
public static final AType<AttrList<EdgeInsnAttr>> EDGE_INSN = new AType<AttrList<EdgeInsnAttr>>();
|
||||
|
||||
public static final AType<ExcHandlerAttr> EXC_HANDLER = new AType<ExcHandlerAttr>();
|
||||
public static final AType<CatchAttr> CATCH_BLOCK = new AType<CatchAttr>();
|
||||
public static final AType<SplitterBlockAttr> SPLITTER_BLOCK = new AType<SplitterBlockAttr>();
|
||||
public static final AType<ForceReturnAttr> FORCE_RETURN = new AType<ForceReturnAttr>();
|
||||
public static final AType<FieldValueAttr> FIELD_VALUE = new AType<FieldValueAttr>();
|
||||
public static final AType<FieldInitAttr> FIELD_INIT = new AType<FieldInitAttr>();
|
||||
public static final AType<FieldReplaceAttr> FIELD_REPLACE = new AType<FieldReplaceAttr>();
|
||||
public static final AType<JadxErrorAttr> JADX_ERROR = new AType<JadxErrorAttr>();
|
||||
public static final AType<MethodInlineAttr> METHOD_INLINE = new AType<MethodInlineAttr>();
|
||||
|
||||
@@ -27,10 +27,13 @@ public abstract class AttrNode implements IAttributeNode {
|
||||
|
||||
@Override
|
||||
public void copyAttributesFrom(AttrNode attrNode) {
|
||||
initStorage().addAll(attrNode.storage);
|
||||
AttributeStorage copyFrom = attrNode.storage;
|
||||
if (!copyFrom.isEmpty()) {
|
||||
initStorage().addAll(copyFrom);
|
||||
}
|
||||
}
|
||||
|
||||
AttributeStorage initStorage() {
|
||||
private AttributeStorage initStorage() {
|
||||
AttributeStorage store = storage;
|
||||
if (store == EMPTY_ATTR_STORAGE) {
|
||||
store = new AttributeStorage();
|
||||
|
||||
@@ -111,6 +111,10 @@ public class AttributeStorage {
|
||||
return list;
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return flags.isEmpty() && attributes.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
List<String> list = getAttributeStrings();
|
||||
|
||||
@@ -53,6 +53,11 @@ public final class EmptyAttrStorage extends AttributeStorage {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "";
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
package jadx.core.dex.attributes.nodes;
|
||||
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.AttrList;
|
||||
import jadx.core.dex.attributes.IAttribute;
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
|
||||
public class EdgeInsnAttr implements IAttribute {
|
||||
|
||||
private final BlockNode start;
|
||||
private final BlockNode end;
|
||||
private final InsnNode insn;
|
||||
|
||||
public static void addEdgeInsn(BlockNode start, BlockNode end, InsnNode insn) {
|
||||
EdgeInsnAttr edgeInsnAttr = new EdgeInsnAttr(start, end, insn);
|
||||
start.addAttr(AType.EDGE_INSN, edgeInsnAttr);
|
||||
end.addAttr(AType.EDGE_INSN, edgeInsnAttr);
|
||||
}
|
||||
|
||||
public EdgeInsnAttr(BlockNode start, BlockNode end, InsnNode insn) {
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
this.insn = insn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AType<AttrList<EdgeInsnAttr>> getType() {
|
||||
return AType.EDGE_INSN;
|
||||
}
|
||||
|
||||
public BlockNode getStart() {
|
||||
return start;
|
||||
}
|
||||
|
||||
public BlockNode getEnd() {
|
||||
return end;
|
||||
}
|
||||
|
||||
public InsnNode getInsn() {
|
||||
return insn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "EDGE_INSN: " + start + "->" + end + " " + insn;
|
||||
}
|
||||
}
|
||||
@@ -2,24 +2,39 @@ package jadx.core.dex.attributes.nodes;
|
||||
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.IAttribute;
|
||||
import jadx.core.dex.info.FieldInfo;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
|
||||
public class FieldReplaceAttr implements IAttribute {
|
||||
|
||||
private final FieldInfo fieldInfo;
|
||||
private final boolean isOuterClass;
|
||||
|
||||
public FieldReplaceAttr(FieldInfo fieldInfo, boolean isOuterClass) {
|
||||
this.fieldInfo = fieldInfo;
|
||||
this.isOuterClass = isOuterClass;
|
||||
public enum ReplaceWith {
|
||||
CLASS_INSTANCE,
|
||||
VAR
|
||||
}
|
||||
|
||||
public FieldInfo getFieldInfo() {
|
||||
return fieldInfo;
|
||||
private final ReplaceWith replaceType;
|
||||
private final Object replaceObj;
|
||||
|
||||
public FieldReplaceAttr(ClassInfo cls) {
|
||||
this.replaceType = ReplaceWith.CLASS_INSTANCE;
|
||||
this.replaceObj = cls;
|
||||
}
|
||||
|
||||
public boolean isOuterClass() {
|
||||
return isOuterClass;
|
||||
public FieldReplaceAttr(InsnArg reg) {
|
||||
this.replaceType = ReplaceWith.VAR;
|
||||
this.replaceObj = reg;
|
||||
}
|
||||
|
||||
public ReplaceWith getReplaceType() {
|
||||
return replaceType;
|
||||
}
|
||||
|
||||
public ClassInfo getClsRef() {
|
||||
return (ClassInfo) replaceObj;
|
||||
}
|
||||
|
||||
public InsnArg getVarRef() {
|
||||
return (InsnArg) replaceObj;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -29,6 +44,6 @@ public class FieldReplaceAttr implements IAttribute {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "REPLACE: " + fieldInfo;
|
||||
return "REPLACE: " + replaceType + " " + replaceObj;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,4 +23,9 @@ public abstract class LineAttrNode extends AttrNode {
|
||||
public void setDecompiledLine(int decompiledLine) {
|
||||
this.decompiledLine = decompiledLine;
|
||||
}
|
||||
|
||||
public void copyLines(LineAttrNode lineAttrNode) {
|
||||
setSourceLine(lineAttrNode.getSourceLine());
|
||||
setDecompiledLine(lineAttrNode.getDecompiledLine());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,6 +125,10 @@ public final class ClassInfo {
|
||||
return pkg;
|
||||
}
|
||||
|
||||
public boolean isDefaultPackage() {
|
||||
return pkg.isEmpty();
|
||||
}
|
||||
|
||||
public String getRawName() {
|
||||
return type.getObject();
|
||||
}
|
||||
@@ -167,7 +171,7 @@ public final class ClassInfo {
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return fullName.hashCode();
|
||||
return type.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,187 @@
|
||||
package jadx.core.dex.info;
|
||||
|
||||
import jadx.api.IJadxArgs;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.instructions.args.LiteralArg;
|
||||
import jadx.core.dex.instructions.args.PrimitiveType;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.DexNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.ResRefField;
|
||||
import jadx.core.dex.nodes.parser.FieldInitAttr;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class ConstStorage {
|
||||
|
||||
private static final class Values {
|
||||
private final Map<Object, FieldNode> values = new HashMap<Object, FieldNode>();
|
||||
private final Set<Object> duplicates = new HashSet<Object>();
|
||||
|
||||
public Map<Object, FieldNode> getValues() {
|
||||
return values;
|
||||
}
|
||||
|
||||
public FieldNode get(Object key) {
|
||||
return values.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if this value is duplicated
|
||||
*/
|
||||
public boolean put(Object value, FieldNode fld) {
|
||||
FieldNode prev = values.put(value, fld);
|
||||
if (prev != null) {
|
||||
values.remove(value);
|
||||
duplicates.add(value);
|
||||
return true;
|
||||
}
|
||||
if (duplicates.contains(value)) {
|
||||
values.remove(value);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean contains(Object value) {
|
||||
return duplicates.contains(value) || values.containsKey(value);
|
||||
}
|
||||
}
|
||||
|
||||
private final boolean replaceEnabled;
|
||||
private final Values globalValues = new Values();
|
||||
private final Map<ClassNode, Values> classes = new HashMap<ClassNode, Values>();
|
||||
|
||||
private Map<Integer, String> resourcesNames = new HashMap<Integer, String>();
|
||||
|
||||
public ConstStorage(IJadxArgs args) {
|
||||
this.replaceEnabled = args.isReplaceConsts();
|
||||
}
|
||||
|
||||
public void processConstFields(ClassNode cls, List<FieldNode> staticFields) {
|
||||
if (!replaceEnabled || staticFields.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
for (FieldNode f : staticFields) {
|
||||
AccessInfo accFlags = f.getAccessFlags();
|
||||
if (accFlags.isStatic() && accFlags.isFinal()) {
|
||||
FieldInitAttr fv = f.get(AType.FIELD_INIT);
|
||||
if (fv != null
|
||||
&& fv.getValue() != null
|
||||
&& fv.getValueType() == FieldInitAttr.InitType.CONST
|
||||
&& fv != FieldInitAttr.NULL_VALUE) {
|
||||
addConstField(cls, f, fv.getValue(), accFlags.isPublic());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addConstField(ClassNode cls, FieldNode fld, Object value, boolean isPublic) {
|
||||
if (isPublic) {
|
||||
globalValues.put(value, fld);
|
||||
} else {
|
||||
getClsValues(cls).put(value, fld);
|
||||
}
|
||||
}
|
||||
|
||||
private Values getClsValues(ClassNode cls) {
|
||||
Values classValues = classes.get(cls);
|
||||
if (classValues == null) {
|
||||
classValues = new Values();
|
||||
classes.put(cls, classValues);
|
||||
}
|
||||
return classValues;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public FieldNode getConstField(ClassNode cls, Object value, boolean searchGlobal) {
|
||||
DexNode dex = cls.dex();
|
||||
if (value instanceof Integer) {
|
||||
String str = resourcesNames.get(value);
|
||||
if (str != null) {
|
||||
return new ResRefField(dex, str.replace('/', '.'));
|
||||
}
|
||||
}
|
||||
if (!replaceEnabled) {
|
||||
return null;
|
||||
}
|
||||
boolean foundInGlobal = globalValues.contains(value);
|
||||
if (foundInGlobal && !searchGlobal) {
|
||||
return null;
|
||||
}
|
||||
ClassNode current = cls;
|
||||
while (current != null) {
|
||||
Values classValues = classes.get(current);
|
||||
if (classValues != null) {
|
||||
FieldNode field = classValues.get(value);
|
||||
if (field != null) {
|
||||
if (foundInGlobal) {
|
||||
return null;
|
||||
}
|
||||
return field;
|
||||
}
|
||||
}
|
||||
ClassInfo parentClass = current.getClassInfo().getParentClass();
|
||||
if (parentClass == null) {
|
||||
break;
|
||||
}
|
||||
current = dex.resolveClass(parentClass);
|
||||
}
|
||||
if (searchGlobal) {
|
||||
return globalValues.get(value);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public FieldNode getConstFieldByLiteralArg(ClassNode cls, LiteralArg arg) {
|
||||
PrimitiveType type = arg.getType().getPrimitiveType();
|
||||
if (type == null) {
|
||||
return null;
|
||||
}
|
||||
long literal = arg.getLiteral();
|
||||
switch (type) {
|
||||
case BOOLEAN:
|
||||
return getConstField(cls, literal == 1, false);
|
||||
case CHAR:
|
||||
return getConstField(cls, (char) literal, Math.abs(literal) > 10);
|
||||
case BYTE:
|
||||
return getConstField(cls, (byte) literal, Math.abs(literal) > 10);
|
||||
case SHORT:
|
||||
return getConstField(cls, (short) literal, Math.abs(literal) > 100);
|
||||
case INT:
|
||||
return getConstField(cls, (int) literal, Math.abs(literal) > 100);
|
||||
case LONG:
|
||||
return getConstField(cls, literal, Math.abs(literal) > 1000);
|
||||
case FLOAT:
|
||||
float f = Float.intBitsToFloat((int) literal);
|
||||
return getConstField(cls, f, f != 0.0);
|
||||
case DOUBLE:
|
||||
double d = Double.longBitsToDouble(literal);
|
||||
return getConstField(cls, d, d != 0);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void setResourcesNames(Map<Integer, String> resourcesNames) {
|
||||
this.resourcesNames = resourcesNames;
|
||||
}
|
||||
|
||||
public Map<Integer, String> getResourcesNames() {
|
||||
return resourcesNames;
|
||||
}
|
||||
|
||||
public Map<Object, FieldNode> getGlobalConstFields() {
|
||||
return globalValues.getValues();
|
||||
}
|
||||
|
||||
public boolean isReplaceEnabled() {
|
||||
return replaceEnabled;
|
||||
}
|
||||
}
|
||||
@@ -18,11 +18,13 @@ public final class MethodInfo {
|
||||
private final ClassInfo declClass;
|
||||
private final String shortId;
|
||||
private String alias;
|
||||
private boolean aliasFromPreset;
|
||||
|
||||
private MethodInfo(DexNode dex, int mthIndex) {
|
||||
MethodId mthId = dex.getMethodId(mthIndex);
|
||||
name = dex.getString(mthId.getNameIndex());
|
||||
alias = name;
|
||||
aliasFromPreset = false;
|
||||
declClass = ClassInfo.fromDex(dex, mthId.getDeclaringClassIndex());
|
||||
|
||||
ProtoId proto = dex.getProtoId(mthId.getProtoIndex());
|
||||
@@ -109,6 +111,14 @@ public final class MethodInfo {
|
||||
return !name.equals(alias);
|
||||
}
|
||||
|
||||
public boolean isAliasFromPreset() {
|
||||
return aliasFromPreset;
|
||||
}
|
||||
|
||||
public void setAliasFromPreset(boolean value) {
|
||||
aliasFromPreset = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = declClass.hashCode();
|
||||
@@ -122,10 +132,7 @@ public final class MethodInfo {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
if (!(obj instanceof MethodInfo)) {
|
||||
return false;
|
||||
}
|
||||
MethodInfo other = (MethodInfo) obj;
|
||||
|
||||
@@ -40,7 +40,6 @@ public class ArithNode extends InsnNode {
|
||||
addReg(insn, 2, type);
|
||||
}
|
||||
}
|
||||
assert getArgsCount() == 2;
|
||||
}
|
||||
|
||||
public ArithNode(ArithOp op, RegisterArg res, InsnArg a, InsnArg b) {
|
||||
|
||||
@@ -16,6 +16,11 @@ public final class ConstClassNode extends InsnNode {
|
||||
return clsType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InsnNode copy() {
|
||||
return copyCommonParams(new ConstClassNode(clsType));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSame(InsnNode obj) {
|
||||
if (this == obj) {
|
||||
|
||||
@@ -15,6 +15,11 @@ public final class ConstStringNode extends InsnNode {
|
||||
return str;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InsnNode copy() {
|
||||
return copyCommonParams(new ConstStringNode(str));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSame(InsnNode obj) {
|
||||
if (this == obj) {
|
||||
|
||||
@@ -14,9 +14,11 @@ import static jadx.core.utils.BlockUtils.selectOther;
|
||||
|
||||
public class IfNode extends GotoNode {
|
||||
|
||||
// change default types priority
|
||||
private static final ArgType ARG_TYPE = ArgType.unknown(
|
||||
PrimitiveType.INT, PrimitiveType.OBJECT, PrimitiveType.ARRAY,
|
||||
PrimitiveType.BOOLEAN, PrimitiveType.SHORT, PrimitiveType.CHAR);
|
||||
PrimitiveType.INT,
|
||||
PrimitiveType.OBJECT, PrimitiveType.ARRAY,
|
||||
PrimitiveType.BOOLEAN, PrimitiveType.BYTE, PrimitiveType.SHORT, PrimitiveType.CHAR);
|
||||
|
||||
protected IfOp op;
|
||||
|
||||
|
||||
@@ -16,6 +16,11 @@ public class IndexInsnNode extends InsnNode {
|
||||
return index;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IndexInsnNode copy() {
|
||||
return copyCommonParams(new IndexInsnNode(insnType, index, getArgsCount()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSame(InsnNode obj) {
|
||||
if (this == obj) {
|
||||
|
||||
@@ -65,6 +65,9 @@ public enum InsnType {
|
||||
ONE_ARG,
|
||||
PHI,
|
||||
|
||||
// merge all arguments in one
|
||||
MERGE,
|
||||
|
||||
// TODO: now multidimensional arrays created using Array.newInstance function
|
||||
NEW_MULTIDIM_ARRAY
|
||||
}
|
||||
|
||||
@@ -36,6 +36,12 @@ public class InvokeNode extends InsnNode {
|
||||
}
|
||||
}
|
||||
|
||||
private InvokeNode(MethodInfo mth, InvokeType invokeType, int argsCount) {
|
||||
super(InsnType.INVOKE, argsCount);
|
||||
this.mth = mth;
|
||||
this.type = invokeType;
|
||||
}
|
||||
|
||||
public InvokeType getInvokeType() {
|
||||
return type;
|
||||
}
|
||||
@@ -44,6 +50,11 @@ public class InvokeNode extends InsnNode {
|
||||
return mth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InsnNode copy() {
|
||||
return copyCommonParams(new InvokeNode(mth, type, getArgsCount()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSame(InsnNode obj) {
|
||||
if (this == obj) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package jadx.core.dex.instructions.args;
|
||||
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@@ -20,7 +21,9 @@ public final class InsnWrapArg extends InsnArg {
|
||||
|
||||
@Override
|
||||
public void setParentInsn(InsnNode parentInsn) {
|
||||
assert parentInsn != wrappedInsn : "Can't wrap instruction info itself: " + parentInsn;
|
||||
if (parentInsn == wrappedInsn) {
|
||||
throw new JadxRuntimeException("Can't wrap instruction info itself: " + parentInsn);
|
||||
}
|
||||
this.parentInsn = parentInsn;
|
||||
}
|
||||
|
||||
|
||||
@@ -77,8 +77,15 @@ public class RegisterArg extends InsnArg implements Named {
|
||||
}
|
||||
|
||||
public RegisterArg duplicate() {
|
||||
RegisterArg dup = new RegisterArg(getRegNum(), getType());
|
||||
dup.setSVar(sVar);
|
||||
return duplicate(getRegNum(), sVar);
|
||||
}
|
||||
|
||||
public RegisterArg duplicate(int regNum, SSAVar sVar) {
|
||||
RegisterArg dup = new RegisterArg(regNum, getType());
|
||||
if (sVar != null) {
|
||||
dup.setSVar(sVar);
|
||||
}
|
||||
dup.copyAttributesFrom(this);
|
||||
return dup;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package jadx.core.dex.instructions.args;
|
||||
|
||||
import jadx.core.dex.attributes.AttrNode;
|
||||
import jadx.core.dex.instructions.PhiInsn;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -8,7 +9,7 @@ import java.util.List;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class SSAVar {
|
||||
public class SSAVar extends AttrNode {
|
||||
|
||||
private final int regNum;
|
||||
private final int version;
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
package jadx.core.dex.instructions.args;
|
||||
|
||||
import jadx.core.dex.attributes.AttrNode;
|
||||
import jadx.core.dex.nodes.DexNode;
|
||||
|
||||
public abstract class Typed {
|
||||
public abstract class Typed extends AttrNode {
|
||||
|
||||
protected ArgType type;
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import jadx.core.dex.regions.conditions.IfCondition;
|
||||
import jadx.core.utils.InsnUtils;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Collection;
|
||||
|
||||
public final class TernaryInsn extends InsnNode {
|
||||
|
||||
@@ -54,7 +54,7 @@ public final class TernaryInsn extends InsnNode {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getRegisterArgs(List<RegisterArg> list) {
|
||||
public void getRegisterArgs(Collection<RegisterArg> list) {
|
||||
super.getRegisterArgs(list);
|
||||
list.addAll(condition.getRegisterArgs());
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.AttrNode;
|
||||
import jadx.core.dex.attributes.nodes.IgnoreEdgeAttr;
|
||||
import jadx.core.dex.attributes.nodes.LoopInfo;
|
||||
import jadx.core.utils.BlockUtils;
|
||||
import jadx.core.utils.EmptyBitSet;
|
||||
import jadx.core.utils.InsnUtils;
|
||||
|
||||
@@ -86,13 +87,8 @@ public class BlockNode extends AttrNode implements IBlock {
|
||||
}
|
||||
List<BlockNode> toRemove = new LinkedList<BlockNode>();
|
||||
for (BlockNode b : sucList) {
|
||||
if (b.contains(AType.EXC_HANDLER)) {
|
||||
if (BlockUtils.isBlockMustBeCleared(b)) {
|
||||
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)) {
|
||||
|
||||
@@ -2,7 +2,6 @@ package jadx.core.dex.nodes;
|
||||
|
||||
import jadx.core.Consts;
|
||||
import jadx.core.codegen.CodeWriter;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.annotations.Annotation;
|
||||
import jadx.core.dex.attributes.nodes.JadxErrorAttr;
|
||||
import jadx.core.dex.attributes.nodes.LineAttrNode;
|
||||
@@ -14,9 +13,8 @@ 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.LiteralArg;
|
||||
import jadx.core.dex.instructions.args.PrimitiveType;
|
||||
import jadx.core.dex.nodes.parser.AnnotationsParser;
|
||||
import jadx.core.dex.nodes.parser.FieldValueAttr;
|
||||
import jadx.core.dex.nodes.parser.FieldInitAttr;
|
||||
import jadx.core.dex.nodes.parser.SignatureParser;
|
||||
import jadx.core.dex.nodes.parser.StaticValuesParser;
|
||||
import jadx.core.utils.exceptions.DecodeException;
|
||||
@@ -24,8 +22,8 @@ import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
@@ -39,9 +37,10 @@ import com.android.dex.ClassData;
|
||||
import com.android.dex.ClassData.Field;
|
||||
import com.android.dex.ClassData.Method;
|
||||
import com.android.dex.ClassDef;
|
||||
import com.android.dex.Dex;
|
||||
import com.android.dx.rop.code.AccessFlags;
|
||||
|
||||
public class ClassNode extends LineAttrNode implements ILoadable {
|
||||
public class ClassNode extends LineAttrNode implements ILoadable, IDexNode {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ClassNode.class);
|
||||
|
||||
private final DexNode dex;
|
||||
@@ -53,7 +52,6 @@ public class ClassNode extends LineAttrNode implements ILoadable {
|
||||
|
||||
private final List<MethodNode> methods;
|
||||
private final List<FieldNode> fields;
|
||||
private Map<Object, FieldNode> constFields = Collections.emptyMap();
|
||||
private List<ClassNode> innerClasses = Collections.emptyList();
|
||||
|
||||
// store decompiled code
|
||||
@@ -64,6 +62,9 @@ public class ClassNode extends LineAttrNode implements ILoadable {
|
||||
private ProcessState state = ProcessState.NOT_LOADED;
|
||||
private final Set<ClassNode> dependencies = new HashSet<ClassNode>();
|
||||
|
||||
// cache maps
|
||||
private Map<MethodInfo, MethodNode> mthInfoMap = Collections.emptyMap();
|
||||
|
||||
public ClassNode(DexNode dex, ClassDef cls) throws DecodeException {
|
||||
this.dex = dex;
|
||||
this.clsInfo = ClassInfo.fromDex(dex, cls.getTypeIndex());
|
||||
@@ -86,10 +87,10 @@ public class ClassNode extends LineAttrNode implements ILoadable {
|
||||
fields = new ArrayList<FieldNode>(fieldsCount);
|
||||
|
||||
for (Method mth : clsData.getDirectMethods()) {
|
||||
methods.add(new MethodNode(this, mth));
|
||||
methods.add(new MethodNode(this, mth, false));
|
||||
}
|
||||
for (Method mth : clsData.getVirtualMethods()) {
|
||||
methods.add(new MethodNode(this, mth));
|
||||
methods.add(new MethodNode(this, mth, true));
|
||||
}
|
||||
|
||||
for (Field f : clsData.getStaticFields()) {
|
||||
@@ -125,6 +126,7 @@ public class ClassNode extends LineAttrNode implements ILoadable {
|
||||
}
|
||||
this.accessFlags = new AccessInfo(accFlagsValue, AFType.CLASS);
|
||||
|
||||
buildCache();
|
||||
} catch (Exception e) {
|
||||
throw new DecodeException("Error decode class: " + clsInfo, e);
|
||||
}
|
||||
@@ -155,28 +157,19 @@ public class ClassNode extends LineAttrNode implements ILoadable {
|
||||
private void loadStaticValues(ClassDef cls, List<FieldNode> staticFields) throws DecodeException {
|
||||
for (FieldNode f : staticFields) {
|
||||
if (f.getAccessFlags().isFinal()) {
|
||||
f.addAttr(new FieldValueAttr(null));
|
||||
f.addAttr(FieldInitAttr.NULL_VALUE);
|
||||
}
|
||||
}
|
||||
|
||||
int offset = cls.getStaticValuesOffset();
|
||||
if (offset != 0) {
|
||||
StaticValuesParser parser = new StaticValuesParser(dex, dex.openSection(offset));
|
||||
int count = parser.processFields(staticFields);
|
||||
constFields = new LinkedHashMap<Object, FieldNode>(count);
|
||||
for (FieldNode f : staticFields) {
|
||||
AccessInfo accFlags = f.getAccessFlags();
|
||||
if (accFlags.isStatic() && accFlags.isFinal()) {
|
||||
FieldValueAttr fv = f.get(AType.FIELD_VALUE);
|
||||
if (fv != null && fv.getValue() != null) {
|
||||
if (accFlags.isPublic()) {
|
||||
dex.getConstFields().put(fv.getValue(), f);
|
||||
}
|
||||
constFields.put(fv.getValue(), f);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (offset == 0) {
|
||||
return;
|
||||
}
|
||||
Dex.Section section = dex.openSection(offset);
|
||||
StaticValuesParser parser = new StaticValuesParser(dex, section);
|
||||
parser.processFields(staticFields);
|
||||
|
||||
// process const fields
|
||||
root().getConstValues().processConstFields(this, staticFields);
|
||||
}
|
||||
|
||||
private void parseClassSignature() {
|
||||
@@ -274,6 +267,13 @@ public class ClassNode extends LineAttrNode implements ILoadable {
|
||||
}
|
||||
}
|
||||
|
||||
private void buildCache() {
|
||||
mthInfoMap = new HashMap<MethodInfo, MethodNode>(methods.size());
|
||||
for (MethodNode mth : methods) {
|
||||
mthInfoMap.put(mth.getMethodInfo(), mth);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ArgType getSuperClass() {
|
||||
return superClass;
|
||||
@@ -299,61 +299,14 @@ public class ClassNode extends LineAttrNode implements ILoadable {
|
||||
return getConstField(obj, true);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public FieldNode getConstField(Object obj, boolean searchGlobal) {
|
||||
ClassNode cn = this;
|
||||
FieldNode field;
|
||||
do {
|
||||
field = cn.constFields.get(obj);
|
||||
}
|
||||
while (field == null
|
||||
&& cn.clsInfo.getParentClass() != null
|
||||
&& (cn = dex.resolveClass(cn.clsInfo.getParentClass())) != null);
|
||||
|
||||
if (field == null && searchGlobal) {
|
||||
field = dex.getConstFields().get(obj);
|
||||
}
|
||||
if (obj instanceof Integer) {
|
||||
String str = dex.root().getResourcesNames().get(obj);
|
||||
if (str != null) {
|
||||
ResRefField resField = new ResRefField(dex, str.replace('/', '.'));
|
||||
if (field == null) {
|
||||
return resField;
|
||||
}
|
||||
if (!field.getName().equals(resField.getName())) {
|
||||
field = resField;
|
||||
}
|
||||
}
|
||||
}
|
||||
return field;
|
||||
return root().getConstValues().getConstField(this, obj, searchGlobal);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public FieldNode getConstFieldByLiteralArg(LiteralArg arg) {
|
||||
PrimitiveType type = arg.getType().getPrimitiveType();
|
||||
if (type == null) {
|
||||
return null;
|
||||
}
|
||||
long literal = arg.getLiteral();
|
||||
switch (type) {
|
||||
case BOOLEAN:
|
||||
return getConstField(literal == 1, false);
|
||||
case CHAR:
|
||||
return getConstField((char) literal, Math.abs(literal) > 10);
|
||||
case BYTE:
|
||||
return getConstField((byte) literal, Math.abs(literal) > 10);
|
||||
case SHORT:
|
||||
return getConstField((short) literal, Math.abs(literal) > 100);
|
||||
case INT:
|
||||
return getConstField((int) literal, Math.abs(literal) > 100);
|
||||
case LONG:
|
||||
return getConstField(literal, Math.abs(literal) > 1000);
|
||||
case FLOAT:
|
||||
float f = Float.intBitsToFloat((int) literal);
|
||||
return getConstField(f, f != 0.0);
|
||||
case DOUBLE:
|
||||
double d = Double.longBitsToDouble(literal);
|
||||
return getConstField(d, d != 0);
|
||||
}
|
||||
return null;
|
||||
return root().getConstValues().getConstFieldByLiteralArg(this, arg);
|
||||
}
|
||||
|
||||
public FieldNode searchFieldById(int id) {
|
||||
@@ -380,12 +333,7 @@ public class ClassNode extends LineAttrNode implements ILoadable {
|
||||
}
|
||||
|
||||
public MethodNode searchMethod(MethodInfo mth) {
|
||||
for (MethodNode m : methods) {
|
||||
if (m.getMethodInfo().equals(mth)) {
|
||||
return m;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return mthInfoMap.get(mth);
|
||||
}
|
||||
|
||||
public MethodNode searchMethodByName(String shortId) {
|
||||
@@ -442,6 +390,12 @@ public class ClassNode extends LineAttrNode implements ILoadable {
|
||||
&& getDefaultConstructor() != null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public MethodNode getClassInitMth() {
|
||||
return searchMethodByName("<clinit>()V");
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public MethodNode getDefaultConstructor() {
|
||||
for (MethodNode mth : methods) {
|
||||
if (mth.isDefaultConstructor()) {
|
||||
@@ -455,10 +409,16 @@ public class ClassNode extends LineAttrNode implements ILoadable {
|
||||
return accessFlags;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DexNode dex() {
|
||||
return dex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RootNode root() {
|
||||
return dex.root();
|
||||
}
|
||||
|
||||
public String getRawName() {
|
||||
return clsInfo.getRawName();
|
||||
}
|
||||
@@ -509,6 +469,24 @@ public class ClassNode extends LineAttrNode implements ILoadable {
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return clsInfo.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o instanceof ClassNode) {
|
||||
ClassNode other = (ClassNode) o;
|
||||
return clsInfo.equals(other.clsInfo);
|
||||
}
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return clsInfo.getFullName();
|
||||
|
||||
@@ -6,7 +6,7 @@ import jadx.core.dex.info.InfoStorage;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.utils.exceptions.DecodeException;
|
||||
import jadx.core.utils.files.InputFile;
|
||||
import jadx.core.utils.files.DexFile;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
@@ -28,25 +28,23 @@ import com.android.dex.MethodId;
|
||||
import com.android.dex.ProtoId;
|
||||
import com.android.dex.TypeList;
|
||||
|
||||
public class DexNode {
|
||||
public class DexNode implements IDexNode {
|
||||
|
||||
public static final int NO_INDEX = -1;
|
||||
|
||||
private final RootNode root;
|
||||
private final Dex dexBuf;
|
||||
private final InputFile file;
|
||||
private final DexFile file;
|
||||
|
||||
private final List<ClassNode> classes = new ArrayList<ClassNode>();
|
||||
private final Map<ClassInfo, ClassNode> clsMap = new HashMap<ClassInfo, ClassNode>();
|
||||
|
||||
private final Map<Object, FieldNode> constFields = new HashMap<Object, FieldNode>();
|
||||
|
||||
private final InfoStorage infoStorage = new InfoStorage();
|
||||
|
||||
public DexNode(RootNode root, InputFile input) {
|
||||
public DexNode(RootNode root, DexFile input) {
|
||||
this.root = root;
|
||||
this.file = input;
|
||||
this.dexBuf = input.getDexBuffer();
|
||||
this.dexBuf = input.getDexBuf();
|
||||
}
|
||||
|
||||
public void loadClasses() throws DecodeException {
|
||||
@@ -155,15 +153,11 @@ public class DexNode {
|
||||
return null;
|
||||
}
|
||||
|
||||
public Map<Object, FieldNode> getConstFields() {
|
||||
return constFields;
|
||||
}
|
||||
|
||||
public InfoStorage getInfoStorage() {
|
||||
return infoStorage;
|
||||
}
|
||||
|
||||
public InputFile getInputFile() {
|
||||
public DexFile getDexFile() {
|
||||
return file;
|
||||
}
|
||||
|
||||
@@ -210,10 +204,16 @@ public class DexNode {
|
||||
return dexBuf.open(offset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RootNode root() {
|
||||
return root;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DexNode dex() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DEX";
|
||||
|
||||
@@ -17,10 +17,8 @@ public class FieldNode extends LineAttrNode {
|
||||
private ArgType type; // store signature
|
||||
|
||||
public FieldNode(ClassNode cls, Field field) {
|
||||
this.parent = cls;
|
||||
this.fieldInfo = FieldInfo.fromDex(cls.dex(), field.getFieldIndex());
|
||||
this.type = fieldInfo.getType();
|
||||
this.accFlags = new AccessInfo(field.getAccessFlags(), AFType.FIELD);
|
||||
this(cls, FieldInfo.fromDex(cls.dex(), field.getFieldIndex()),
|
||||
field.getAccessFlags());
|
||||
}
|
||||
|
||||
public FieldNode(ClassNode cls, FieldInfo fieldInfo, int accessFlags) {
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package jadx.core.dex.nodes;
|
||||
|
||||
public interface IDexNode {
|
||||
|
||||
DexNode dex();
|
||||
|
||||
RootNode root();
|
||||
}
|
||||
|
||||
@@ -5,18 +5,30 @@ import jadx.core.dex.instructions.InsnType;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.InsnWrapArg;
|
||||
import jadx.core.dex.instructions.args.LiteralArg;
|
||||
import jadx.core.dex.instructions.args.NamedArg;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.instructions.args.SSAVar;
|
||||
import jadx.core.utils.InsnUtils;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import com.android.dx.io.instructions.DecodedInstruction;
|
||||
import com.rits.cloning.Cloner;
|
||||
|
||||
public class InsnNode extends LineAttrNode {
|
||||
|
||||
private static final Cloner INSN_CLONER = new Cloner();
|
||||
|
||||
static {
|
||||
INSN_CLONER.dontClone(ArgType.class, SSAVar.class, LiteralArg.class, NamedArg.class);
|
||||
INSN_CLONER.dontCloneInstanceOf(RegisterArg.class);
|
||||
}
|
||||
|
||||
protected final InsnType insnType;
|
||||
|
||||
private RegisterArg result;
|
||||
@@ -146,12 +158,12 @@ public class InsnNode extends LineAttrNode {
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
public void getRegisterArgs(List<RegisterArg> list) {
|
||||
public void getRegisterArgs(Collection<RegisterArg> collection) {
|
||||
for (InsnArg arg : this.getArguments()) {
|
||||
if (arg.isRegister()) {
|
||||
list.add((RegisterArg) arg);
|
||||
collection.add((RegisterArg) arg);
|
||||
} else if (arg.isInsnWrap()) {
|
||||
((InsnWrapArg) arg).getWrapInsn().getRegisterArgs(list);
|
||||
((InsnWrapArg) arg).getWrapInsn().getRegisterArgs(collection);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -193,6 +205,21 @@ public class InsnNode extends LineAttrNode {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean canReorderRecursive() {
|
||||
if (!canReorder()) {
|
||||
return false;
|
||||
}
|
||||
for (InsnArg arg : this.getArguments()) {
|
||||
if (arg.isInsnWrap()) {
|
||||
InsnNode wrapInsn = ((InsnWrapArg) arg).getWrapInsn();
|
||||
if (!wrapInsn.canReorderRecursive()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return InsnUtils.formatOffset(offset) + ": "
|
||||
@@ -224,8 +251,54 @@ public class InsnNode extends LineAttrNode {
|
||||
if (this == other) {
|
||||
return true;
|
||||
}
|
||||
return insnType == other.insnType
|
||||
&& arguments.size() == other.arguments.size();
|
||||
if (insnType != other.insnType
|
||||
|| arguments.size() != other.arguments.size()) {
|
||||
return false;
|
||||
}
|
||||
// check wrapped instructions
|
||||
int size = arguments.size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
InsnArg arg = arguments.get(i);
|
||||
InsnArg otherArg = other.arguments.get(i);
|
||||
if (arg.isInsnWrap()) {
|
||||
if (!otherArg.isInsnWrap()) {
|
||||
return false;
|
||||
}
|
||||
InsnNode wrapInsn = ((InsnWrapArg) arg).getWrapInsn();
|
||||
InsnNode otherWrapInsn = ((InsnWrapArg) otherArg).getWrapInsn();
|
||||
if (!wrapInsn.isSame(otherWrapInsn)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected <T extends InsnNode> T copyCommonParams(T copy) {
|
||||
copy.setResult(result);
|
||||
if (copy.getArgsCount() == 0) {
|
||||
for (InsnArg arg : this.getArguments()) {
|
||||
if (arg.isInsnWrap()) {
|
||||
InsnNode wrapInsn = ((InsnWrapArg) arg).getWrapInsn();
|
||||
copy.addArg(InsnArg.wrapArg(wrapInsn.copy()));
|
||||
} else {
|
||||
copy.addArg(arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
copy.copyAttributesFrom(this);
|
||||
copy.copyLines(this);
|
||||
copy.setOffset(this.getOffset());
|
||||
return copy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make copy of InsnNode object.
|
||||
*/
|
||||
public InsnNode copy() {
|
||||
if (this.getClass() == InsnNode.class) {
|
||||
return copyCommonParams(new InsnNode(insnType, getArgsCount()));
|
||||
}
|
||||
return INSN_CLONER.deepClone(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -43,7 +44,7 @@ import com.android.dex.Code;
|
||||
import com.android.dex.Code.CatchHandler;
|
||||
import com.android.dex.Code.Try;
|
||||
|
||||
public class MethodNode extends LineAttrNode implements ILoadable {
|
||||
public class MethodNode extends LineAttrNode implements ILoadable, IDexNode {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MethodNode.class);
|
||||
|
||||
private final MethodInfo mthInfo;
|
||||
@@ -56,6 +57,7 @@ public class MethodNode extends LineAttrNode implements ILoadable {
|
||||
private int codeSize;
|
||||
private int debugInfoOffset;
|
||||
private boolean noCode;
|
||||
private boolean methodIsVirtual;
|
||||
|
||||
private ArgType retType;
|
||||
private RegisterArg thisArg;
|
||||
@@ -71,12 +73,13 @@ public class MethodNode extends LineAttrNode implements ILoadable {
|
||||
private List<ExceptionHandler> exceptionHandlers = Collections.emptyList();
|
||||
private List<LoopInfo> loops = Collections.emptyList();
|
||||
|
||||
public MethodNode(ClassNode classNode, Method mthData) {
|
||||
public MethodNode(ClassNode classNode, Method mthData, boolean isVirtual) {
|
||||
this.mthInfo = MethodInfo.fromDex(classNode.dex(), mthData.getMethodIndex());
|
||||
this.parentClass = classNode;
|
||||
this.accFlags = new AccessInfo(mthData.getAccessFlags(), AFType.METHOD);
|
||||
this.noCode = mthData.getCodeOffset() == 0;
|
||||
this.methodData = noCode ? null : mthData;
|
||||
this.methodIsVirtual = isVirtual;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -440,6 +443,7 @@ public class MethodNode extends LineAttrNode implements ILoadable {
|
||||
loops.add(loop);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public LoopInfo getLoopForBlock(BlockNode block) {
|
||||
if (loops.isEmpty()) {
|
||||
return null;
|
||||
@@ -509,11 +513,11 @@ public class MethodNode extends LineAttrNode implements ILoadable {
|
||||
}
|
||||
|
||||
String name = getName();
|
||||
List<MethodNode> methods = parentClass.getMethods();
|
||||
for (MethodNode method : methods) {
|
||||
for (MethodNode method : parentClass.getMethods()) {
|
||||
MethodInfo otherMthInfo = method.mthInfo;
|
||||
if (this != method
|
||||
&& method.getName().equals(name)
|
||||
&& method.mthInfo.getArgumentsTypes().size() == argsCount) {
|
||||
&& otherMthInfo.getArgumentsTypes().size() == argsCount
|
||||
&& otherMthInfo.getName().equals(name)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -538,6 +542,10 @@ public class MethodNode extends LineAttrNode implements ILoadable {
|
||||
return result;
|
||||
}
|
||||
|
||||
public boolean isVirtual() {
|
||||
return methodIsVirtual;
|
||||
}
|
||||
|
||||
public int getRegsCount() {
|
||||
return regsCount;
|
||||
}
|
||||
@@ -546,9 +554,8 @@ public class MethodNode extends LineAttrNode implements ILoadable {
|
||||
return debugInfoOffset;
|
||||
}
|
||||
|
||||
public SSAVar makeNewSVar(int regNum, int[] versions, @NotNull RegisterArg arg) {
|
||||
SSAVar var = new SSAVar(regNum, versions[regNum], arg);
|
||||
versions[regNum]++;
|
||||
public SSAVar makeNewSVar(int regNum, int version, @NotNull RegisterArg assignArg) {
|
||||
SSAVar var = new SSAVar(regNum, version, assignArg);
|
||||
if (sVars.isEmpty()) {
|
||||
sVars = new ArrayList<SSAVar>();
|
||||
}
|
||||
@@ -556,6 +563,17 @@ public class MethodNode extends LineAttrNode implements ILoadable {
|
||||
return var;
|
||||
}
|
||||
|
||||
public int getNextSVarVersion(int regNum) {
|
||||
int v = -1;
|
||||
for (SSAVar sVar : sVars) {
|
||||
if (sVar.getRegNum() == regNum) {
|
||||
v = Math.max(v, sVar.getVersion());
|
||||
}
|
||||
}
|
||||
v++;
|
||||
return v;
|
||||
}
|
||||
|
||||
public void removeSVar(SSAVar var) {
|
||||
sVars.remove(var);
|
||||
}
|
||||
@@ -576,10 +594,16 @@ public class MethodNode extends LineAttrNode implements ILoadable {
|
||||
this.region = region;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DexNode dex() {
|
||||
return parentClass.dex();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RootNode root() {
|
||||
return dex().root();
|
||||
}
|
||||
|
||||
public MethodInfo getMethodInfo() {
|
||||
return mthInfo;
|
||||
}
|
||||
|
||||
@@ -6,19 +6,22 @@ import jadx.api.ResourceType;
|
||||
import jadx.api.ResourcesLoader;
|
||||
import jadx.core.clsp.ClspGraph;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.info.ConstStorage;
|
||||
import jadx.core.utils.ErrorsCounter;
|
||||
import jadx.core.utils.StringUtils;
|
||||
import jadx.core.utils.android.AndroidResourcesUtils;
|
||||
import jadx.core.utils.exceptions.DecodeException;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
import jadx.core.utils.files.DexFile;
|
||||
import jadx.core.utils.files.InputFile;
|
||||
import jadx.core.xmlgen.ResContainer;
|
||||
import jadx.core.xmlgen.ResTableParser;
|
||||
import jadx.core.xmlgen.ResourceStorage;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
@@ -29,9 +32,10 @@ public class RootNode {
|
||||
|
||||
private final ErrorsCounter errorsCounter = new ErrorsCounter();
|
||||
private final IJadxArgs args;
|
||||
private final StringUtils stringUtils;
|
||||
private final ConstStorage constValues;
|
||||
|
||||
private List<DexNode> dexNodes;
|
||||
private Map<Integer, String> resourcesNames = new HashMap<Integer, String>();
|
||||
@Nullable
|
||||
private String appPackage;
|
||||
private ClassNode appResClass;
|
||||
@@ -39,18 +43,22 @@ public class RootNode {
|
||||
|
||||
public RootNode(IJadxArgs args) {
|
||||
this.args = args;
|
||||
this.stringUtils = new StringUtils(args);
|
||||
this.constValues = new ConstStorage(args);
|
||||
}
|
||||
|
||||
public void load(List<InputFile> dexFiles) throws DecodeException {
|
||||
dexNodes = new ArrayList<DexNode>(dexFiles.size());
|
||||
for (InputFile dex : dexFiles) {
|
||||
DexNode dexNode;
|
||||
try {
|
||||
dexNode = new DexNode(this, dex);
|
||||
} catch (Exception e) {
|
||||
throw new DecodeException("Error decode file: " + dex, e);
|
||||
public void load(List<InputFile> inputFiles) throws DecodeException {
|
||||
dexNodes = new ArrayList<DexNode>();
|
||||
for (InputFile input : inputFiles) {
|
||||
for (DexFile dexFile : input.getDexFiles()) {
|
||||
try {
|
||||
LOG.debug("Load: {}", dexFile);
|
||||
DexNode dexNode = new DexNode(this, dexFile);
|
||||
dexNodes.add(dexNode);
|
||||
} catch (Exception e) {
|
||||
throw new DecodeException("Error decode file: " + dexFile, e);
|
||||
}
|
||||
}
|
||||
dexNodes.add(dexNode);
|
||||
}
|
||||
for (DexNode dexNode : dexNodes) {
|
||||
dexNode.loadClasses();
|
||||
@@ -74,7 +82,7 @@ public class RootNode {
|
||||
try {
|
||||
ResourcesLoader.decodeStream(arsc, new ResourcesLoader.ResourceDecoder() {
|
||||
@Override
|
||||
public Object decode(long size, InputStream is) throws IOException {
|
||||
public ResContainer decode(long size, InputStream is) throws IOException {
|
||||
parser.decode(is);
|
||||
return null;
|
||||
}
|
||||
@@ -85,29 +93,12 @@ public class RootNode {
|
||||
}
|
||||
|
||||
ResourceStorage resStorage = parser.getResStorage();
|
||||
resourcesNames = resStorage.getResourcesNames();
|
||||
constValues.setResourcesNames(resStorage.getResourcesNames());
|
||||
appPackage = resStorage.getAppPackage();
|
||||
}
|
||||
|
||||
public void initAppResClass() {
|
||||
ClassNode resCls;
|
||||
if (appPackage == null) {
|
||||
appResClass = makeClass("R");
|
||||
return;
|
||||
}
|
||||
String fullName = appPackage + ".R";
|
||||
resCls = searchClassByName(fullName);
|
||||
if (resCls != null) {
|
||||
appResClass = resCls;
|
||||
} else {
|
||||
appResClass = makeClass(fullName);
|
||||
}
|
||||
}
|
||||
|
||||
private ClassNode makeClass(String clsName) {
|
||||
DexNode firstDex = dexNodes.get(0);
|
||||
ClassInfo r = ClassInfo.fromName(firstDex, clsName);
|
||||
return new ClassNode(firstDex, r);
|
||||
appResClass = AndroidResourcesUtils.searchAppResClass(this);
|
||||
}
|
||||
|
||||
public void initClassPath() throws DecodeException {
|
||||
@@ -162,6 +153,18 @@ public class RootNode {
|
||||
return null;
|
||||
}
|
||||
|
||||
public List<ClassNode> searchClassByShortName(String shortName) {
|
||||
List<ClassNode> list = new ArrayList<ClassNode>();
|
||||
for (DexNode dexNode : dexNodes) {
|
||||
for (ClassNode cls : dexNode.getClasses()) {
|
||||
if (cls.getClassInfo().getShortName().equals(shortName)) {
|
||||
list.add(cls);
|
||||
}
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
public List<DexNode> getDexNodes() {
|
||||
return dexNodes;
|
||||
}
|
||||
@@ -174,10 +177,6 @@ public class RootNode {
|
||||
return errorsCounter;
|
||||
}
|
||||
|
||||
public Map<Integer, String> getResourcesNames() {
|
||||
return resourcesNames;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getAppPackage() {
|
||||
return appPackage;
|
||||
@@ -190,4 +189,12 @@ public class RootNode {
|
||||
public IJadxArgs getArgs() {
|
||||
return args;
|
||||
}
|
||||
|
||||
public StringUtils getStringUtils() {
|
||||
return stringUtils;
|
||||
}
|
||||
|
||||
public ConstStorage getConstValues() {
|
||||
return constValues;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,10 +11,14 @@ import jadx.core.utils.exceptions.DecodeException;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.android.dex.Dex.Section;
|
||||
|
||||
public class DebugInfoParser {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DebugInfoParser.class);
|
||||
private static final int DBG_END_SEQUENCE = 0x00;
|
||||
private static final int DBG_ADVANCE_PC = 0x01;
|
||||
private static final int DBG_ADVANCE_LINE = 0x02;
|
||||
@@ -58,13 +62,13 @@ public class DebugInfoParser {
|
||||
|
||||
int paramsCount = section.readUleb128();
|
||||
List<RegisterArg> mthArgs = mth.getArguments(false);
|
||||
assert paramsCount == mthArgs.size();
|
||||
|
||||
for (int i = 0; i < paramsCount; i++) {
|
||||
int id = section.readUleb128() - 1;
|
||||
if (id != DexNode.NO_INDEX) {
|
||||
String name = dex.getString(id);
|
||||
mthArgs.get(i).setName(name);
|
||||
if (i < mthArgs.size()) {
|
||||
mthArgs.get(i).setName(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
package jadx.core.dex.nodes.parser;
|
||||
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.IAttribute;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
|
||||
public class FieldInitAttr implements IAttribute {
|
||||
|
||||
public static final FieldInitAttr NULL_VALUE = constValue(null);
|
||||
|
||||
public enum InitType {
|
||||
CONST,
|
||||
INSN
|
||||
|
||||
}
|
||||
|
||||
private final Object value;
|
||||
private final InitType valueType;
|
||||
private final MethodNode insnMth;
|
||||
|
||||
private FieldInitAttr(InitType valueType, Object value, MethodNode insnMth) {
|
||||
this.value = value;
|
||||
this.valueType = valueType;
|
||||
this.insnMth = insnMth;
|
||||
}
|
||||
|
||||
public static FieldInitAttr constValue(Object value) {
|
||||
return new FieldInitAttr(InitType.CONST, value, null);
|
||||
}
|
||||
|
||||
public static FieldInitAttr insnValue(MethodNode mth, InsnNode insn) {
|
||||
return new FieldInitAttr(InitType.INSN, insn, mth);
|
||||
}
|
||||
|
||||
public Object getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public InsnNode getInsn() {
|
||||
return (InsnNode) value;
|
||||
}
|
||||
|
||||
public InitType getValueType() {
|
||||
return valueType;
|
||||
}
|
||||
|
||||
public MethodNode getInsnMth() {
|
||||
return insnMth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AType<FieldInitAttr> getType() {
|
||||
return AType.FIELD_INIT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "V=" + value;
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package jadx.core.dex.nodes.parser;
|
||||
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.IAttribute;
|
||||
|
||||
public class FieldValueAttr implements IAttribute {
|
||||
|
||||
private final Object value;
|
||||
|
||||
public FieldValueAttr(Object value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AType<FieldValueAttr> getType() {
|
||||
return AType.FIELD_VALUE;
|
||||
}
|
||||
|
||||
public Object getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "V=" + value;
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,9 @@ public class StaticValuesParser extends EncValueParser {
|
||||
int count = Leb128.readUnsignedLeb128(in);
|
||||
for (int i = 0; i < count; i++) {
|
||||
Object value = parseValue();
|
||||
fields.get(i).addAttr(new FieldValueAttr(value));
|
||||
if (i < fields.size()) {
|
||||
fields.get(i).addAttr(FieldInitAttr.constValue(value));
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import jadx.core.dex.nodes.IBranchRegion;
|
||||
import jadx.core.dex.nodes.IContainer;
|
||||
import jadx.core.dex.nodes.IRegion;
|
||||
import jadx.core.dex.regions.AbstractRegion;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
@@ -20,7 +21,9 @@ public final class IfRegion extends AbstractRegion implements IBranchRegion {
|
||||
|
||||
public IfRegion(IRegion parent, BlockNode header) {
|
||||
super(parent);
|
||||
assert header.getInstructions().size() == 1;
|
||||
if (header.getInstructions().size() != 1) {
|
||||
throw new JadxRuntimeException("Expected only one instruction in 'if' header");
|
||||
}
|
||||
this.header = header;
|
||||
this.condition = IfCondition.fromIfBlock(header);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import jadx.core.dex.info.ClassInfo;
|
||||
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.Utils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -63,6 +64,8 @@ public class TryCatchBlock {
|
||||
|
||||
private void unbindHandler(ExceptionHandler handler) {
|
||||
for (BlockNode block : handler.getBlocks()) {
|
||||
// skip synthetic loop exit blocks
|
||||
BlockUtils.skipPredSyntheticPaths(block);
|
||||
block.add(AFlag.SKIP);
|
||||
ExcHandlerAttr excHandlerAttr = block.get(AType.EXC_HANDLER);
|
||||
if (excHandlerAttr != null) {
|
||||
|
||||
@@ -11,12 +11,14 @@ import jadx.core.dex.instructions.IndexInsnNode;
|
||||
import jadx.core.dex.instructions.InsnType;
|
||||
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.instructions.mods.ConstructorInsn;
|
||||
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.BlockUtils;
|
||||
import jadx.core.utils.InstructionRemover;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
|
||||
@@ -44,8 +46,6 @@ public class ClassModifier extends AbstractVisitor {
|
||||
removeSyntheticMethods(cls);
|
||||
removeEmptyMethods(cls);
|
||||
|
||||
checkFieldsInit(cls);
|
||||
|
||||
markAnonymousClass(cls);
|
||||
return false;
|
||||
}
|
||||
@@ -76,8 +76,7 @@ public class ClassModifier extends AbstractVisitor {
|
||||
}
|
||||
}
|
||||
if (found != 0) {
|
||||
FieldInfo replace = FieldInfo.from(cls.dex(), parentClass, "this", parentClass.getType());
|
||||
field.addAttr(new FieldReplaceAttr(replace, true));
|
||||
field.addAttr(new FieldReplaceAttr(parentClass));
|
||||
field.add(AFlag.DONT_GENERATE);
|
||||
}
|
||||
}
|
||||
@@ -136,13 +135,24 @@ public class ClassModifier extends AbstractVisitor {
|
||||
mth.add(AFlag.DONT_GENERATE);
|
||||
continue;
|
||||
}
|
||||
// remove synthetic constructor for inner non-static classes
|
||||
// remove synthetic constructor for inner classes
|
||||
if (af.isSynthetic() && af.isConstructor() && mth.getBasicBlocks().size() == 2) {
|
||||
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).isEmpty()) {
|
||||
mth.removeFirstArgument();
|
||||
List<RegisterArg> args = mth.getArguments(false);
|
||||
if (constr.isThis() && !args.isEmpty()) {
|
||||
// remove first arg for non-static class (references to outer class)
|
||||
if (args.get(0).getType().equals(cls.getParentClass().getClassInfo().getType())) {
|
||||
args.get(0).add(AFlag.SKIP_ARG);
|
||||
}
|
||||
// remove unused args
|
||||
for (RegisterArg arg : args) {
|
||||
SSAVar sVar = arg.getSVar();
|
||||
if (sVar != null && sVar.getUseCount() == 0) {
|
||||
arg.add(AFlag.SKIP_ARG);
|
||||
}
|
||||
}
|
||||
mth.add(AFlag.DONT_GENERATE);
|
||||
}
|
||||
}
|
||||
@@ -171,55 +181,15 @@ public class ClassModifier extends AbstractVisitor {
|
||||
|
||||
// remove public empty constructors
|
||||
if (af.isConstructor()
|
||||
&& af.isPublic()
|
||||
&& (af.isPublic() || af.isStatic())
|
||||
&& mth.getArguments(false).isEmpty()
|
||||
&& !mth.contains(AType.JADX_ERROR)) {
|
||||
List<BlockNode> bb = mth.getBasicBlocks();
|
||||
if (bb == null || bb.isEmpty() || allBlocksEmpty(bb)) {
|
||||
if (bb == null || bb.isEmpty() || BlockUtils.isAllBlocksEmpty(bb)) {
|
||||
mth.add(AFlag.DONT_GENERATE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean allBlocksEmpty(List<BlockNode> blocks) {
|
||||
for (BlockNode block : blocks) {
|
||||
if (!block.getInstructions().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void checkFieldsInit(ClassNode cls) {
|
||||
MethodNode clinit = cls.searchMethodByName("<clinit>()V");
|
||||
if (clinit == null
|
||||
|| !clinit.getAccessFlags().isStatic()
|
||||
|| clinit.isNoCode()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (BlockNode block : clinit.getBasicBlocks()) {
|
||||
for (InsnNode insn : block.getInstructions()) {
|
||||
if (insn.getType() == InsnType.SPUT) {
|
||||
processStaticFieldAssign(cls, (IndexInsnNode) insn);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove field initialization if it assign in "<clinit>" method
|
||||
*/
|
||||
private static void processStaticFieldAssign(ClassNode cls, IndexInsnNode insn) {
|
||||
FieldInfo field = (FieldInfo) insn.getIndex();
|
||||
String thisClass = cls.getClassInfo().getFullName();
|
||||
if (field.getDeclClass().getFullName().equals(thisClass)) {
|
||||
FieldNode fn = cls.searchField(field);
|
||||
if (fn != null && fn.getAccessFlags().isFinal()) {
|
||||
fn.remove(AType.FIELD_VALUE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -23,8 +23,6 @@ import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class CodeShrinker extends AbstractVisitor {
|
||||
|
||||
@Override
|
||||
@@ -38,6 +36,7 @@ public class CodeShrinker extends AbstractVisitor {
|
||||
}
|
||||
for (BlockNode block : mth.getBasicBlocks()) {
|
||||
shrinkBlock(mth, block);
|
||||
simplifyMoveInsns(block);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,7 +209,9 @@ public class CodeShrinker extends AbstractVisitor {
|
||||
// }
|
||||
SSAVar sVar = arg.getSVar();
|
||||
// allow inline only one use arg or 'this'
|
||||
if (sVar == null || sVar.getVariableUseCount() != 1 && !arg.isThis()) {
|
||||
if (sVar == null
|
||||
|| sVar.getVariableUseCount() != 1 && !arg.isThis()
|
||||
|| sVar.contains(AFlag.DONT_INLINE)) {
|
||||
continue;
|
||||
}
|
||||
InsnNode assignInsn = sVar.getAssign().getParentInsn();
|
||||
@@ -229,53 +230,28 @@ public class CodeShrinker extends AbstractVisitor {
|
||||
if (assignBlock != null
|
||||
&& assignInsn != arg.getParentInsn()
|
||||
&& canMoveBetweenBlocks(assignInsn, assignBlock, block, argsInfo.getInsn())) {
|
||||
if (inline(arg, assignInsn, assignBlock, mth)) {
|
||||
InsnList.remove(assignBlock, assignInsn);
|
||||
}
|
||||
inline(arg, assignInsn, assignBlock);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!wrapList.isEmpty()) {
|
||||
for (WrapInfo wrapInfo : wrapList) {
|
||||
inline(wrapInfo.getArg(), wrapInfo.getInsn(), block, mth);
|
||||
}
|
||||
for (WrapInfo wrapInfo : wrapList) {
|
||||
insnList.remove(wrapInfo.getInsn());
|
||||
inline(wrapInfo.getArg(), wrapInfo.getInsn(), block);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean inline(RegisterArg arg, InsnNode insn, @Nullable BlockNode block, MethodNode mth) {
|
||||
private static boolean inline(RegisterArg arg, InsnNode insn, BlockNode block) {
|
||||
InsnNode parentInsn = arg.getParentInsn();
|
||||
// replace move instruction if needed
|
||||
if (parentInsn != null) {
|
||||
switch (parentInsn.getType()) {
|
||||
case MOVE: {
|
||||
if (block == null) {
|
||||
block = BlockUtils.getBlockByInsn(mth, parentInsn);
|
||||
}
|
||||
if (block != null) {
|
||||
int index = InsnList.getIndex(block.getInstructions(), parentInsn);
|
||||
if (index != -1) {
|
||||
insn.setResult(parentInsn.getResult());
|
||||
insn.copyAttributesFrom(parentInsn);
|
||||
insn.setOffset(parentInsn.getOffset());
|
||||
|
||||
block.getInstructions().set(index, insn);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case RETURN: {
|
||||
parentInsn.setSourceLine(insn.getSourceLine());
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (parentInsn != null && parentInsn.getType() == InsnType.RETURN) {
|
||||
parentInsn.setSourceLine(insn.getSourceLine());
|
||||
}
|
||||
// simple case
|
||||
return arg.wrapInstruction(insn) != null;
|
||||
boolean replaced = arg.wrapInstruction(insn) != null;
|
||||
if (replaced) {
|
||||
InsnList.remove(block, insn);
|
||||
}
|
||||
return replaced;
|
||||
}
|
||||
|
||||
private static boolean canMoveBetweenBlocks(InsnNode assignInsn, BlockNode assignBlock,
|
||||
@@ -318,4 +294,24 @@ public class CodeShrinker extends AbstractVisitor {
|
||||
}
|
||||
throw new JadxRuntimeException("Can't process instruction move : " + assignBlock);
|
||||
}
|
||||
|
||||
private static void simplifyMoveInsns(BlockNode block) {
|
||||
List<InsnNode> insns = block.getInstructions();
|
||||
int size = insns.size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
InsnNode insn = insns.get(i);
|
||||
if (insn.getType() == InsnType.MOVE) {
|
||||
// replace 'move' with wrapped insn
|
||||
InsnArg arg = insn.getArg(0);
|
||||
if (arg.isInsnWrap()) {
|
||||
InsnNode wrapInsn = ((InsnWrapArg) arg).getWrapInsn();
|
||||
wrapInsn.setResult(insn.getResult());
|
||||
wrapInsn.copyAttributesFrom(insn);
|
||||
wrapInsn.setOffset(insn.getOffset());
|
||||
wrapInsn.remove(AFlag.WRAPPED);
|
||||
block.getInstructions().set(i, wrapInsn);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,7 +103,9 @@ public class ConstInlineVisitor extends AbstractVisitor {
|
||||
int replaceCount = 0;
|
||||
for (RegisterArg arg : use) {
|
||||
InsnNode useInsn = arg.getParentInsn();
|
||||
if (useInsn == null || useInsn.getType() == InsnType.PHI) {
|
||||
if (useInsn == null
|
||||
|| useInsn.getType() == InsnType.PHI
|
||||
|| useInsn.getType() == InsnType.MERGE) {
|
||||
continue;
|
||||
}
|
||||
LiteralArg litArg;
|
||||
|
||||
@@ -18,6 +18,7 @@ public class DebugInfoVisitor extends AbstractVisitor {
|
||||
DebugInfoParser debugInfoParser = new DebugInfoParser(mth, debugOffset, insnArr);
|
||||
debugInfoParser.process();
|
||||
|
||||
// set method source line from first instruction
|
||||
if (insnArr.length != 0) {
|
||||
for (InsnNode insn : insnArr) {
|
||||
if (insn != null) {
|
||||
@@ -30,7 +31,7 @@ public class DebugInfoVisitor extends AbstractVisitor {
|
||||
}
|
||||
}
|
||||
if (!mth.getReturnType().equals(ArgType.VOID)) {
|
||||
// fix debug for splitter 'return' instructions
|
||||
// fix debug info for splitter 'return' instructions
|
||||
for (BlockNode exit : mth.getExitBlocks()) {
|
||||
InsnNode ret = BlockUtils.getLastInsn(exit);
|
||||
if (ret == null) {
|
||||
|
||||
@@ -23,8 +23,13 @@ import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class DotGraphVisitor extends AbstractVisitor {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DotGraphVisitor.class);
|
||||
|
||||
private static final String NL = "\\l";
|
||||
private static final boolean PRINT_DOMINATORS = false;
|
||||
|
||||
@@ -52,6 +57,8 @@ public class DotGraphVisitor extends AbstractVisitor {
|
||||
this.dir = outDir;
|
||||
this.useRegions = useRegions;
|
||||
this.rawInsn = rawInsn;
|
||||
LOG.debug("DOT {}{}graph dump dir: {}",
|
||||
useRegions ? "regions " : "", rawInsn ? "raw " : "", outDir.getAbsolutePath());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,251 @@
|
||||
package jadx.core.dex.visitors;
|
||||
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
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.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.InsnWrapArg;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
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.dex.nodes.parser.FieldInitAttr;
|
||||
import jadx.core.utils.BlockUtils;
|
||||
import jadx.core.utils.InstructionRemover;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
@JadxVisitor(
|
||||
name = "ExtractFieldInit",
|
||||
desc = "Move duplicated field initialization from constructors",
|
||||
runAfter = ModVisitor.class,
|
||||
runBefore = ClassModifier.class
|
||||
)
|
||||
public class ExtractFieldInit extends AbstractVisitor {
|
||||
|
||||
@Override
|
||||
public boolean visit(ClassNode cls) throws JadxException {
|
||||
if (cls.isEnum()) {
|
||||
return false;
|
||||
}
|
||||
for (ClassNode inner : cls.getInnerClasses()) {
|
||||
visit(inner);
|
||||
}
|
||||
checkStaticFieldsInit(cls);
|
||||
moveStaticFieldsInit(cls);
|
||||
moveCommonFieldsInit(cls);
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void checkStaticFieldsInit(ClassNode cls) {
|
||||
MethodNode clinit = cls.getClassInitMth();
|
||||
if (clinit == null
|
||||
|| !clinit.getAccessFlags().isStatic()
|
||||
|| clinit.isNoCode()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (BlockNode block : clinit.getBasicBlocks()) {
|
||||
for (InsnNode insn : block.getInstructions()) {
|
||||
if (insn.getType() == InsnType.SPUT) {
|
||||
processStaticFieldAssign(cls, (IndexInsnNode) insn);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove final field in place initialization if it assign in class init method
|
||||
*/
|
||||
private static void processStaticFieldAssign(ClassNode cls, IndexInsnNode insn) {
|
||||
FieldInfo field = (FieldInfo) insn.getIndex();
|
||||
String thisClass = cls.getClassInfo().getFullName();
|
||||
if (field.getDeclClass().getFullName().equals(thisClass)) {
|
||||
FieldNode fn = cls.searchField(field);
|
||||
if (fn != null && fn.getAccessFlags().isFinal()) {
|
||||
fn.remove(AType.FIELD_INIT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void moveStaticFieldsInit(ClassNode cls) {
|
||||
MethodNode classInitMth = cls.getClassInitMth();
|
||||
if (classInitMth == null) {
|
||||
return;
|
||||
}
|
||||
for (FieldNode field : cls.getFields()) {
|
||||
if (field.contains(AFlag.DONT_GENERATE)) {
|
||||
continue;
|
||||
}
|
||||
if (field.getAccessFlags().isStatic()) {
|
||||
List<InsnNode> initInsns = getFieldAssigns(classInitMth, field, InsnType.SPUT);
|
||||
if (initInsns.size() == 1) {
|
||||
InsnNode insn = initInsns.get(0);
|
||||
if (checkInsn(insn)) {
|
||||
InstructionRemover.remove(classInitMth, insn);
|
||||
addFieldInitAttr(classInitMth, field, insn);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class InitInfo {
|
||||
private final MethodNode constrMth;
|
||||
private final List<InsnNode> putInsns = new ArrayList<InsnNode>();
|
||||
|
||||
private InitInfo(MethodNode constrMth) {
|
||||
this.constrMth = constrMth;
|
||||
}
|
||||
|
||||
public MethodNode getConstrMth() {
|
||||
return constrMth;
|
||||
}
|
||||
|
||||
public List<InsnNode> getPutInsns() {
|
||||
return putInsns;
|
||||
}
|
||||
}
|
||||
|
||||
private static void moveCommonFieldsInit(ClassNode cls) {
|
||||
List<MethodNode> constrList = getConstructorsList(cls);
|
||||
if (constrList.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
List<InitInfo> infoList = new ArrayList<InitInfo>(constrList.size());
|
||||
for (MethodNode constrMth : constrList) {
|
||||
if (constrMth.isNoCode() || constrMth.getBasicBlocks().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
InitInfo info = new InitInfo(constrMth);
|
||||
infoList.add(info);
|
||||
// TODO: check not only first block
|
||||
BlockNode blockNode = constrMth.getBasicBlocks().get(0);
|
||||
for (InsnNode insn : blockNode.getInstructions()) {
|
||||
if (insn.getType() == InsnType.IPUT && checkInsn(insn)) {
|
||||
info.getPutInsns().add(insn);
|
||||
} else if (!info.getPutInsns().isEmpty()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// compare collected instructions
|
||||
InitInfo common = null;
|
||||
for (InitInfo info : infoList) {
|
||||
if (common == null) {
|
||||
common = info;
|
||||
} else if (!compareInsns(common.getPutInsns(), info.getPutInsns())) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (common == null) {
|
||||
return;
|
||||
}
|
||||
Set<FieldInfo> fields = new HashSet<FieldInfo>();
|
||||
for (InsnNode insn : common.getPutInsns()) {
|
||||
FieldInfo fieldInfo = (FieldInfo) ((IndexInsnNode) insn).getIndex();
|
||||
FieldNode field = cls.dex().resolveField(fieldInfo);
|
||||
if (field == null) {
|
||||
return;
|
||||
}
|
||||
if (!fields.add(fieldInfo)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
// all checks passed
|
||||
for (InitInfo info : infoList) {
|
||||
for (InsnNode putInsn : info.getPutInsns()) {
|
||||
InstructionRemover.remove(info.getConstrMth(), putInsn);
|
||||
}
|
||||
}
|
||||
for (InsnNode insn : common.getPutInsns()) {
|
||||
FieldInfo fieldInfo = (FieldInfo) ((IndexInsnNode) insn).getIndex();
|
||||
FieldNode field = cls.dex().resolveField(fieldInfo);
|
||||
addFieldInitAttr(common.getConstrMth(), field, insn);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean compareInsns(List<InsnNode> base, List<InsnNode> other) {
|
||||
if (base.size() != other.size()) {
|
||||
return false;
|
||||
}
|
||||
int count = base.size();
|
||||
for (int i = 0; i < count; i++) {
|
||||
InsnNode baseInsn = base.get(i);
|
||||
InsnNode otherInsn = other.get(i);
|
||||
if (!baseInsn.isSame(otherInsn)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean checkInsn(InsnNode insn) {
|
||||
InsnArg arg = insn.getArg(0);
|
||||
if (arg.isInsnWrap()) {
|
||||
InsnNode wrapInsn = ((InsnWrapArg) arg).getWrapInsn();
|
||||
if (!wrapInsn.canReorderRecursive() && insn.contains(AType.CATCH_BLOCK)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return arg.isLiteral() || arg.isThis();
|
||||
}
|
||||
Set<RegisterArg> regs = new HashSet<RegisterArg>();
|
||||
insn.getRegisterArgs(regs);
|
||||
if (!regs.isEmpty()) {
|
||||
for (RegisterArg reg : regs) {
|
||||
if (!reg.isThis()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static List<MethodNode> getConstructorsList(ClassNode cls) {
|
||||
List<MethodNode> list = new ArrayList<MethodNode>();
|
||||
for (MethodNode mth : cls.getMethods()) {
|
||||
AccessInfo accFlags = mth.getAccessFlags();
|
||||
if (!accFlags.isStatic() && accFlags.isConstructor()) {
|
||||
list.add(mth);
|
||||
if (BlockUtils.isAllBlocksEmpty(mth.getBasicBlocks())) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
private static List<InsnNode> getFieldAssigns(MethodNode mth, FieldNode field, InsnType putInsn) {
|
||||
if (mth.isNoCode()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<InsnNode> assignInsns = new ArrayList<InsnNode>();
|
||||
for (BlockNode block : mth.getBasicBlocks()) {
|
||||
for (InsnNode insn : block.getInstructions()) {
|
||||
if (insn.getType() == putInsn) {
|
||||
FieldInfo putNode = (FieldInfo) ((IndexInsnNode) insn).getIndex();
|
||||
if (putNode.equals(field.getFieldInfo())) {
|
||||
assignInsns.add(insn);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return assignInsns;
|
||||
}
|
||||
|
||||
private static void addFieldInitAttr(MethodNode classInitMth, FieldNode field, InsnNode insn) {
|
||||
InsnNode assignInsn = InsnNode.wrapArg(insn.getArg(0));
|
||||
field.addAttr(FieldInitAttr.insnValue(classInitMth, assignInsn));
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,11 @@ package jadx.core.dex.visitors;
|
||||
|
||||
import jadx.core.codegen.TypeGen;
|
||||
import jadx.core.deobf.NameMapper;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.info.FieldInfo;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.ArithNode;
|
||||
import jadx.core.dex.instructions.ConstClassNode;
|
||||
@@ -34,7 +38,10 @@ import jadx.core.utils.InstructionRemover;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -43,6 +50,11 @@ import org.slf4j.LoggerFactory;
|
||||
* Visitor for modify method instructions
|
||||
* (remove, replace, process exception handlers)
|
||||
*/
|
||||
@JadxVisitor(
|
||||
name = "ModVisitor",
|
||||
desc = "Modify method instructions",
|
||||
runBefore = CodeShrinker.class
|
||||
)
|
||||
public class ModVisitor extends AbstractVisitor {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ModVisitor.class);
|
||||
|
||||
@@ -197,7 +209,6 @@ public class ModVisitor extends AbstractVisitor {
|
||||
remover.add(insn);
|
||||
return;
|
||||
}
|
||||
replaceInsn(block, insnNumber, co);
|
||||
if (co.isNewInstance()) {
|
||||
InsnNode newInstInsn = removeAssignChain(instArgAssignInsn, remover, InsnType.NEW_INSTANCE);
|
||||
if (newInstInsn != null) {
|
||||
@@ -217,8 +228,107 @@ public class ModVisitor extends AbstractVisitor {
|
||||
}
|
||||
ConstructorInsn replace = processConstructor(mth, co);
|
||||
if (replace != null) {
|
||||
replaceInsn(block, insnNumber, replace);
|
||||
co = replace;
|
||||
}
|
||||
replaceInsn(block, insnNumber, co);
|
||||
|
||||
processAnonymousConstructor(mth, co);
|
||||
}
|
||||
|
||||
private static void processAnonymousConstructor(MethodNode mth, ConstructorInsn co) {
|
||||
MethodInfo callMth = co.getCallMth();
|
||||
MethodNode callMthNode = mth.dex().resolveMethod(callMth);
|
||||
if (callMthNode == null) {
|
||||
return;
|
||||
}
|
||||
ClassNode classNode = callMthNode.getParentClass();
|
||||
ClassInfo classInfo = classNode.getClassInfo();
|
||||
ClassNode parentClass = mth.getParentClass();
|
||||
if (!classInfo.isInner()
|
||||
|| !Character.isDigit(classInfo.getShortName().charAt(0))
|
||||
|| !parentClass.getInnerClasses().contains(classNode)) {
|
||||
return;
|
||||
}
|
||||
if (!classNode.getAccessFlags().isStatic()
|
||||
&& (callMth.getArgsCount() == 0
|
||||
|| !callMth.getArgumentsTypes().get(0).equals(parentClass.getClassInfo().getType()))) {
|
||||
return;
|
||||
}
|
||||
// TODO: calculate this constructor and other constructor usage
|
||||
Map<InsnArg, FieldNode> argsMap = getArgsToFieldsMapping(callMthNode, co);
|
||||
if (argsMap.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// all checks passed
|
||||
classNode.add(AFlag.ANONYMOUS_CLASS);
|
||||
callMthNode.add(AFlag.DONT_GENERATE);
|
||||
for (Map.Entry<InsnArg, FieldNode> entry : argsMap.entrySet()) {
|
||||
FieldNode field = entry.getValue();
|
||||
if (field == null) {
|
||||
continue;
|
||||
}
|
||||
InsnArg arg = entry.getKey();
|
||||
field.addAttr(new FieldReplaceAttr(arg));
|
||||
field.add(AFlag.DONT_GENERATE);
|
||||
if (arg.isRegister()) {
|
||||
RegisterArg reg = (RegisterArg) arg;
|
||||
SSAVar sVar = reg.getSVar();
|
||||
if (sVar != null) {
|
||||
sVar.add(AFlag.FINAL);
|
||||
sVar.add(AFlag.DONT_INLINE);
|
||||
}
|
||||
reg.add(AFlag.SKIP_ARG);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Map<InsnArg, FieldNode> getArgsToFieldsMapping(MethodNode callMthNode, ConstructorInsn co) {
|
||||
Map<InsnArg, FieldNode> map = new LinkedHashMap<InsnArg, FieldNode>();
|
||||
ClassNode parentClass = callMthNode.getParentClass();
|
||||
List<RegisterArg> argList = callMthNode.getArguments(false);
|
||||
int startArg = parentClass.getAccessFlags().isStatic() ? 0 : 1;
|
||||
int argsCount = argList.size();
|
||||
for (int i = startArg; i < argsCount; i++) {
|
||||
RegisterArg arg = argList.get(i);
|
||||
InsnNode useInsn = getParentInsnSkipMove(arg);
|
||||
if (useInsn == null) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
FieldNode fieldNode = null;
|
||||
if (useInsn.getType() == InsnType.IPUT) {
|
||||
FieldInfo field = (FieldInfo) ((IndexInsnNode) useInsn).getIndex();
|
||||
fieldNode = parentClass.searchField(field);
|
||||
if (fieldNode == null || !fieldNode.getAccessFlags().isSynthetic()) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
} else if (useInsn.getType() == InsnType.CONSTRUCTOR) {
|
||||
ConstructorInsn superConstr = (ConstructorInsn) useInsn;
|
||||
if (!superConstr.isSuper()) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
} else {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
map.put(co.getArg(i), fieldNode);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
private static InsnNode getParentInsnSkipMove(RegisterArg arg) {
|
||||
SSAVar sVar = arg.getSVar();
|
||||
if (sVar.getUseCount() != 1) {
|
||||
return null;
|
||||
}
|
||||
RegisterArg useArg = sVar.getUseList().get(0);
|
||||
InsnNode parentInsn = useArg.getParentInsn();
|
||||
if (parentInsn == null) {
|
||||
return null;
|
||||
}
|
||||
if (parentInsn.getType() == InsnType.MOVE) {
|
||||
return getParentInsnSkipMove(parentInsn.getResult());
|
||||
}
|
||||
return parentInsn;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -250,6 +360,11 @@ public class ModVisitor extends AbstractVisitor {
|
||||
ArgType insnArrayType = insn.getResult().getType();
|
||||
ArgType insnElementType = insnArrayType.getArrayElement();
|
||||
ArgType elType = insn.getElementType();
|
||||
if (!elType.isTypeKnown() && insnElementType.isPrimitive()) {
|
||||
if (elType.contains(insnElementType.getPrimitiveType())) {
|
||||
elType = insnElementType;
|
||||
}
|
||||
}
|
||||
if (!elType.equals(insnElementType) && !insnArrayType.equals(ArgType.OBJECT)) {
|
||||
ErrorsCounter.methodError(mth,
|
||||
"Incorrect type for fill-array insn " + InsnUtils.formatOffset(insn.getOffset())
|
||||
|
||||
@@ -21,6 +21,11 @@ import java.util.List;
|
||||
* most of this modification breaks register dependencies,
|
||||
* so this pass must be just before CodeGen.
|
||||
*/
|
||||
@JadxVisitor(
|
||||
name = "PrepareForCodeGen",
|
||||
desc = "Prepare instructions for code generation pass",
|
||||
runAfter = {CodeShrinker.class, ClassModifier.class}
|
||||
)
|
||||
public class PrepareForCodeGen extends AbstractVisitor {
|
||||
|
||||
@Override
|
||||
|
||||
@@ -14,11 +14,13 @@ import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
import jadx.core.utils.files.InputFile;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.apache.commons.io.IOCase;
|
||||
|
||||
public class RenameVisitor extends AbstractVisitor {
|
||||
@@ -30,7 +32,13 @@ public class RenameVisitor extends AbstractVisitor {
|
||||
@Override
|
||||
public void init(RootNode root) {
|
||||
IJadxArgs args = root.getArgs();
|
||||
File deobfMapFile = new File(args.getOutDir(), "deobf_map.jobf");
|
||||
|
||||
InputFile firstInputFile = root.getDexNodes().get(0).getDexFile().getInputFile();
|
||||
final String firstInputFileName = firstInputFile.getFile().getAbsolutePath();
|
||||
final String inputPath = FilenameUtils.getFullPathNoEndSeparator(firstInputFileName);
|
||||
final String inputName = FilenameUtils.getBaseName(firstInputFileName);
|
||||
|
||||
File deobfMapFile = new File(inputPath, inputName + ".jobf");
|
||||
deobfuscator = new Deobfuscator(args, root.getDexNodes(), deobfMapFile);
|
||||
boolean deobfuscationOn = args.isDeobfuscationOn();
|
||||
if (deobfuscationOn) {
|
||||
|
||||
@@ -77,25 +77,7 @@ public class SimplifyVisitor extends AbstractVisitor {
|
||||
return convertFieldArith(mth, insn);
|
||||
|
||||
case CHECK_CAST:
|
||||
InsnArg castArg = insn.getArg(0);
|
||||
ArgType argType = castArg.getType();
|
||||
|
||||
// Don't removes CHECK_CAST for wrapped INVOKE if invoked method returns different type
|
||||
if (castArg.isInsnWrap()) {
|
||||
InsnNode wrapInsn = ((InsnWrapArg) castArg).getWrapInsn();
|
||||
if (wrapInsn.getType() == InsnType.INVOKE) {
|
||||
argType = ((InvokeNode) wrapInsn).getCallMth().getReturnType();
|
||||
}
|
||||
}
|
||||
ArgType castToType = (ArgType) ((IndexInsnNode) insn).getIndex();
|
||||
if (!ArgType.isCastNeeded(mth.dex(), argType, castToType)) {
|
||||
InsnNode insnNode = new InsnNode(InsnType.MOVE, 1);
|
||||
insnNode.setOffset(insn.getOffset());
|
||||
insnNode.setResult(insn.getResult());
|
||||
insnNode.addArg(castArg);
|
||||
return insnNode;
|
||||
}
|
||||
break;
|
||||
return processCast(mth, insn);
|
||||
|
||||
case MOVE:
|
||||
InsnArg firstArg = insn.getArg(0);
|
||||
@@ -114,6 +96,28 @@ public class SimplifyVisitor extends AbstractVisitor {
|
||||
return null;
|
||||
}
|
||||
|
||||
private static InsnNode processCast(MethodNode mth, InsnNode insn) {
|
||||
InsnArg castArg = insn.getArg(0);
|
||||
ArgType argType = castArg.getType();
|
||||
|
||||
// Don't removes CHECK_CAST for wrapped INVOKE if invoked method returns different type
|
||||
if (castArg.isInsnWrap()) {
|
||||
InsnNode wrapInsn = ((InsnWrapArg) castArg).getWrapInsn();
|
||||
if (wrapInsn.getType() == InsnType.INVOKE) {
|
||||
argType = ((InvokeNode) wrapInsn).getCallMth().getReturnType();
|
||||
}
|
||||
}
|
||||
ArgType castToType = (ArgType) ((IndexInsnNode) insn).getIndex();
|
||||
if (ArgType.isCastNeeded(mth.dex(), argType, castToType)) {
|
||||
return null;
|
||||
}
|
||||
InsnNode insnNode = new InsnNode(InsnType.MOVE, 1);
|
||||
insnNode.setOffset(insn.getOffset());
|
||||
insnNode.setResult(insn.getResult());
|
||||
insnNode.addArg(castArg);
|
||||
return insnNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simplify 'cmp' instruction in if condition
|
||||
*/
|
||||
|
||||
+127
-20
@@ -162,7 +162,7 @@ public class BlockFinallyExtract extends AbstractVisitor {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* 'finally' extract confirmed, run remove steps */
|
||||
// 'finally' extract confirmed, run remove steps
|
||||
|
||||
LiveVarAnalysis laBefore = null;
|
||||
boolean runReMap = isReMapNeeded(removes);
|
||||
@@ -191,7 +191,7 @@ public class BlockFinallyExtract extends AbstractVisitor {
|
||||
|
||||
RegisterArg resArg = me.getResult();
|
||||
BlockNode succ = handlerBlock.getCleanSuccessors().get(0);
|
||||
if (laAfter.isLive(succ.getId(), resArg.getRegNum())) {
|
||||
if (laAfter.isLive(succ, resArg.getRegNum())) {
|
||||
// kill variable
|
||||
InsnNode kill = new InsnNode(InsnType.NOP, 0);
|
||||
kill.setResult(resArg);
|
||||
@@ -224,34 +224,59 @@ public class BlockFinallyExtract extends AbstractVisitor {
|
||||
BitSet processed = new BitSet(mth.getRegsCount());
|
||||
for (BlocksRemoveInfo removeInfo : removes) {
|
||||
processed.clear();
|
||||
BlockNode insertBlock = removeInfo.getStart().getSecond();
|
||||
BlocksPair start = removeInfo.getStart();
|
||||
BlockNode insertBlockBefore = start.getFirst();
|
||||
BlockNode insertBlock = start.getSecond();
|
||||
if (removeInfo.getRegMap().isEmpty() || insertBlock == null) {
|
||||
continue;
|
||||
}
|
||||
for (Map.Entry<RegisterArg, RegisterArg> entry : removeInfo.getRegMap().entrySet()) {
|
||||
RegisterArg from = entry.getKey();
|
||||
int regNum = from.getRegNum();
|
||||
if (!processed.get(regNum)) {
|
||||
if (laBefore.isLive(insertBlock.getId(), regNum)) {
|
||||
RegisterArg fromReg = entry.getKey();
|
||||
RegisterArg toReg = entry.getValue();
|
||||
int fromRegNum = fromReg.getRegNum();
|
||||
int toRegNum = toReg.getRegNum();
|
||||
if (!processed.get(fromRegNum)) {
|
||||
boolean liveFromBefore = laBefore.isLive(insertBlockBefore, fromRegNum);
|
||||
boolean liveFromAfter = laAfter.isLive(insertBlock, fromRegNum);
|
||||
// boolean liveToBefore = laBefore.isLive(insertBlock, toRegNum);
|
||||
boolean liveToAfter = laAfter.isLive(insertBlock, toRegNum);
|
||||
if (liveToAfter && liveFromBefore) {
|
||||
// merge 'to' and 'from' registers
|
||||
InsnNode merge = new InsnNode(InsnType.MERGE, 2);
|
||||
merge.setResult(toReg.duplicate());
|
||||
merge.addArg(toReg.duplicate());
|
||||
merge.addArg(fromReg.duplicate());
|
||||
injectInsn(mth, insertBlock, merge);
|
||||
} else if (liveFromBefore) {
|
||||
// remap variable
|
||||
RegisterArg to = entry.getValue();
|
||||
InsnNode move = new InsnNode(InsnType.MOVE, 1);
|
||||
move.setResult(to);
|
||||
move.addArg(from);
|
||||
insertBlock.getInstructions().add(move);
|
||||
} else if (laAfter.isLive(insertBlock.getId(), regNum)) {
|
||||
move.setResult(toReg.duplicate());
|
||||
move.addArg(fromReg.duplicate());
|
||||
injectInsn(mth, insertBlock, move);
|
||||
} else if (liveFromAfter) {
|
||||
// kill variable
|
||||
InsnNode kill = new InsnNode(InsnType.NOP, 0);
|
||||
kill.setResult(from);
|
||||
kill.setResult(fromReg.duplicate());
|
||||
kill.add(AFlag.REMOVE);
|
||||
insertBlock.getInstructions().add(0, kill);
|
||||
injectInsn(mth, insertBlock, kill);
|
||||
}
|
||||
processed.set(regNum);
|
||||
processed.set(fromRegNum);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void injectInsn(MethodNode mth, BlockNode insertBlock, InsnNode insn) {
|
||||
insn.add(AFlag.SYNTHETIC);
|
||||
if (insertBlock.getInstructions().isEmpty()) {
|
||||
insertBlock.getInstructions().add(insn);
|
||||
} else {
|
||||
BlockNode succBlock = splitBlock(mth, insertBlock, 0);
|
||||
BlockNode predBlock = succBlock.getPredecessors().get(0);
|
||||
predBlock.getInstructions().add(insn);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isReMapNeeded(List<BlocksRemoveInfo> removes) {
|
||||
for (BlocksRemoveInfo removeInfo : removes) {
|
||||
if (!removeInfo.getRegMap().isEmpty()) {
|
||||
@@ -270,11 +295,41 @@ public class BlockFinallyExtract extends AbstractVisitor {
|
||||
if (removeInfo == null) {
|
||||
return null;
|
||||
}
|
||||
if (removeInfo.getOuts().size() != 1) {
|
||||
LOG.debug("Unexpected finally block outs count: {}", removeInfo.getOuts());
|
||||
return null;
|
||||
Set<BlocksPair> outs = removeInfo.getOuts();
|
||||
if (outs.size() == 1) {
|
||||
return removeInfo;
|
||||
}
|
||||
return removeInfo;
|
||||
// check if several 'return' blocks maps to one out
|
||||
if (mergeReturns(mth, outs)) {
|
||||
return removeInfo;
|
||||
}
|
||||
LOG.debug("Unexpected finally block outs count: {}", outs);
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean mergeReturns(MethodNode mth, Set<BlocksPair> outs) {
|
||||
Set<BlockNode> rightOuts = new HashSet<BlockNode>();
|
||||
boolean allReturns = true;
|
||||
for (BlocksPair outPair : outs) {
|
||||
BlockNode first = outPair.getFirst();
|
||||
if (!first.isReturnBlock()) {
|
||||
allReturns = false;
|
||||
}
|
||||
rightOuts.add(outPair.getSecond());
|
||||
}
|
||||
if (!allReturns || rightOuts.size() != 1) {
|
||||
return false;
|
||||
}
|
||||
Iterator<BlocksPair> it = outs.iterator();
|
||||
while (it.hasNext()) {
|
||||
BlocksPair out = it.next();
|
||||
BlockNode returnBlock = out.getFirst();
|
||||
if (!returnBlock.contains(AFlag.ORIG_RETURN)) {
|
||||
markForRemove(mth, returnBlock);
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static BlocksRemoveInfo checkFromFirstBlock(BlockNode remBlock, BlockNode startBlock, BitSet bs) {
|
||||
@@ -453,7 +508,7 @@ public class BlockFinallyExtract extends AbstractVisitor {
|
||||
// change start block in removeInfo
|
||||
removeInfo.getProcessed().remove(removeInfo.getStart());
|
||||
BlocksPair newStart = new BlocksPair(remBlock, startBlock);
|
||||
removeInfo.setStart(newStart);
|
||||
// removeInfo.setStart(newStart);
|
||||
removeInfo.getProcessed().add(newStart);
|
||||
}
|
||||
// split end block
|
||||
@@ -529,6 +584,11 @@ public class BlockFinallyExtract extends AbstractVisitor {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Split one block into connected 2 blocks with same connections.
|
||||
*
|
||||
* @return new successor block
|
||||
*/
|
||||
private static BlockNode splitBlock(MethodNode mth, BlockNode block, int splitIndex) {
|
||||
BlockNode newBlock = BlockSplitter.startNewBlock(mth, -1);
|
||||
|
||||
@@ -654,6 +714,53 @@ public class BlockFinallyExtract extends AbstractVisitor {
|
||||
markForRemove(mth, mb);
|
||||
edgeAttr.getBlocks().remove(mb);
|
||||
}
|
||||
mergeSyntheticPredecessors(mth, origReturnBlock);
|
||||
}
|
||||
|
||||
private static void mergeSyntheticPredecessors(MethodNode mth, BlockNode block) {
|
||||
List<BlockNode> preds = new ArrayList<BlockNode>(block.getPredecessors());
|
||||
Iterator<BlockNode> it = preds.iterator();
|
||||
while (it.hasNext()) {
|
||||
BlockNode predBlock = it.next();
|
||||
if (!predBlock.isSynthetic()) {
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
if (preds.size() < 2) {
|
||||
return;
|
||||
}
|
||||
BlockNode commonBlock = null;
|
||||
for (BlockNode predBlock : preds) {
|
||||
List<BlockNode> successors = predBlock.getSuccessors();
|
||||
if (successors.size() != 2) {
|
||||
return;
|
||||
}
|
||||
BlockNode cmnBlk = BlockUtils.selectOtherSafe(block, successors);
|
||||
if (commonBlock == null) {
|
||||
commonBlock = cmnBlk;
|
||||
} else if (cmnBlk != commonBlock) {
|
||||
return;
|
||||
}
|
||||
if (!predBlock.contains(AType.IGNORE_EDGE)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (commonBlock == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// merge confirmed
|
||||
BlockNode mergeBlock = null;
|
||||
for (BlockNode predBlock : preds) {
|
||||
if (mergeBlock == null) {
|
||||
mergeBlock = predBlock;
|
||||
continue;
|
||||
}
|
||||
for (BlockNode remPred : predBlock.getPredecessors()) {
|
||||
connect(remPred, mergeBlock);
|
||||
}
|
||||
markForRemove(mth, predBlock);
|
||||
}
|
||||
}
|
||||
|
||||
private static BlockNode getFinallyOutBlock(List<BlockNode> exitBlocks) {
|
||||
|
||||
@@ -367,7 +367,8 @@ public class BlockProcessor extends AbstractVisitor {
|
||||
}
|
||||
|
||||
private static void cleanExitNodes(MethodNode mth) {
|
||||
for (Iterator<BlockNode> iterator = mth.getExitBlocks().iterator(); iterator.hasNext(); ) {
|
||||
Iterator<BlockNode> iterator = mth.getExitBlocks().iterator();
|
||||
while (iterator.hasNext()) {
|
||||
BlockNode exitBlock = iterator.next();
|
||||
if (exitBlock.getPredecessors().isEmpty()) {
|
||||
mth.getBasicBlocks().remove(exitBlock);
|
||||
|
||||
@@ -7,7 +7,8 @@ import jadx.core.dex.nodes.MethodNode;
|
||||
public abstract class AbstractRegionVisitor implements IRegionVisitor {
|
||||
|
||||
@Override
|
||||
public void enterRegion(MethodNode mth, IRegion region) {
|
||||
public boolean enterRegion(MethodNode mth, IRegion region) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -64,13 +64,14 @@ public class CheckRegions extends AbstractVisitor {
|
||||
// check loop conditions
|
||||
DepthRegionTraversal.traverse(mth, new AbstractRegionVisitor() {
|
||||
@Override
|
||||
public void enterRegion(MethodNode mth, IRegion region) {
|
||||
public boolean enterRegion(MethodNode mth, IRegion region) {
|
||||
if (region instanceof LoopRegion) {
|
||||
BlockNode loopHeader = ((LoopRegion) region).getHeader();
|
||||
if (loopHeader != null && loopHeader.getInstructions().size() != 1) {
|
||||
ErrorsCounter.methodError(mth, "Incorrect condition in loop: " + loopHeader);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -23,9 +23,9 @@ public class CleanRegions {
|
||||
}
|
||||
IRegionVisitor removeEmptyBlocks = new AbstractRegionVisitor() {
|
||||
@Override
|
||||
public void enterRegion(MethodNode mth, IRegion region) {
|
||||
public boolean enterRegion(MethodNode mth, IRegion region) {
|
||||
if (!(region instanceof Region)) {
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
for (Iterator<IContainer> it = region.getSubBlocks().iterator(); it.hasNext(); ) {
|
||||
@@ -42,6 +42,7 @@ public class CleanRegions {
|
||||
}
|
||||
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
DepthRegionTraversal.traverse(mth, removeEmptyBlocks);
|
||||
|
||||
@@ -53,9 +53,10 @@ public class DepthRegionTraversal {
|
||||
visitor.processBlock(mth, (IBlock) container);
|
||||
} else if (container instanceof IRegion) {
|
||||
IRegion region = (IRegion) container;
|
||||
visitor.enterRegion(mth, region);
|
||||
for (IContainer subCont : region.getSubBlocks()) {
|
||||
traverseInternal(mth, visitor, subCont);
|
||||
if (visitor.enterRegion(mth, region)) {
|
||||
for (IContainer subCont : region.getSubBlocks()) {
|
||||
traverseInternal(mth, visitor, subCont);
|
||||
}
|
||||
}
|
||||
visitor.leaveRegion(mth, region);
|
||||
}
|
||||
|
||||
@@ -8,7 +8,10 @@ public interface IRegionVisitor {
|
||||
|
||||
void processBlock(MethodNode mth, IBlock container);
|
||||
|
||||
void enterRegion(MethodNode mth, IRegion region);
|
||||
/**
|
||||
* @return true for traverse sub-blocks, false otherwise.
|
||||
*/
|
||||
boolean enterRegion(MethodNode mth, IRegion region);
|
||||
|
||||
void leaveRegion(MethodNode mth, IRegion region);
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import static jadx.core.dex.visitors.regions.RegionMaker.isEqualPaths;
|
||||
import static jadx.core.dex.visitors.regions.RegionMaker.isReturnBlocks;
|
||||
import static jadx.core.dex.visitors.regions.RegionMaker.isEqualReturnBlocks;
|
||||
import static jadx.core.utils.BlockUtils.getNextBlock;
|
||||
import static jadx.core.utils.BlockUtils.isPathExists;
|
||||
|
||||
@@ -277,7 +277,7 @@ public class IfMakerHelper {
|
||||
}
|
||||
|
||||
private static boolean isSameBlocks(BlockNode first, BlockNode second) {
|
||||
return first == second || isReturnBlocks(first, second);
|
||||
return first == second || isEqualReturnBlocks(first, second);
|
||||
}
|
||||
|
||||
static void confirmMerge(IfInfo info) {
|
||||
|
||||
@@ -38,10 +38,11 @@ public class IfRegionVisitor extends AbstractVisitor implements IRegionVisitor,
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enterRegion(MethodNode mth, IRegion region) {
|
||||
public boolean enterRegion(MethodNode mth, IRegion region) {
|
||||
if (region instanceof IfRegion) {
|
||||
processIfRegion(mth, (IfRegion) region);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -47,10 +47,11 @@ public class LoopRegionVisitor extends AbstractVisitor implements IRegionVisitor
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enterRegion(MethodNode mth, IRegion region) {
|
||||
public boolean enterRegion(MethodNode mth, IRegion region) {
|
||||
if (region instanceof LoopRegion) {
|
||||
processLoopRegion(mth, (LoopRegion) region);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void processLoopRegion(MethodNode mth, LoopRegion loopRegion) {
|
||||
|
||||
@@ -55,7 +55,7 @@ public class ProcessTryCatchRegions extends AbstractRegionVisitor {
|
||||
}
|
||||
|
||||
private static void searchTryCatchDominators(MethodNode mth, Map<BlockNode, TryCatchBlock> tryBlocksMap) {
|
||||
final Set<TryCatchBlock> tryBlocks = new HashSet<TryCatchBlock>();
|
||||
Set<TryCatchBlock> tryBlocks = new HashSet<TryCatchBlock>();
|
||||
// collect all try/catch blocks
|
||||
for (BlockNode block : mth.getBasicBlocks()) {
|
||||
CatchAttr c = block.get(AType.CATCH_BLOCK);
|
||||
|
||||
@@ -256,17 +256,17 @@ public class ProcessVariables extends AbstractVisitor {
|
||||
continue;
|
||||
}
|
||||
IRegion parent = region;
|
||||
boolean declare = false;
|
||||
boolean declared = false;
|
||||
while (parent != null) {
|
||||
if (canDeclareInRegion(u, region, regionsOrder)) {
|
||||
declareVar(region, u.getArg());
|
||||
declare = true;
|
||||
declared = true;
|
||||
break;
|
||||
}
|
||||
region = parent;
|
||||
parent = region.getParent();
|
||||
}
|
||||
if (!declare) {
|
||||
if (!declared) {
|
||||
declareVar(mth.getRegion(), u.getArg());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package jadx.core.dex.visitors.regions;
|
||||
import jadx.core.Consts;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.nodes.EdgeInsnAttr;
|
||||
import jadx.core.dex.attributes.nodes.LoopInfo;
|
||||
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
|
||||
import jadx.core.dex.instructions.IfNode;
|
||||
@@ -11,6 +12,8 @@ import jadx.core.dex.instructions.SwitchNode;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.dex.nodes.Edge;
|
||||
import jadx.core.dex.nodes.IBlock;
|
||||
import jadx.core.dex.nodes.IContainer;
|
||||
import jadx.core.dex.nodes.IRegion;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
@@ -29,6 +32,7 @@ import jadx.core.utils.ErrorsCounter;
|
||||
import jadx.core.utils.InstructionRemover;
|
||||
import jadx.core.utils.RegionUtils;
|
||||
import jadx.core.utils.exceptions.JadxOverflowException;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.BitSet;
|
||||
@@ -353,9 +357,12 @@ public class RegionMaker {
|
||||
return false;
|
||||
}
|
||||
List<BlockNode> simplePath = BlockUtils.buildSimplePath(exit);
|
||||
if (!simplePath.isEmpty()
|
||||
&& simplePath.get(simplePath.size() - 1).contains(AFlag.RETURN)) {
|
||||
return false;
|
||||
if (!simplePath.isEmpty()) {
|
||||
BlockNode lastBlock = simplePath.get(simplePath.size() - 1);
|
||||
if (lastBlock.contains(AFlag.RETURN)
|
||||
|| lastBlock.getSuccessors().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// check if there no outer switch (TODO: very expensive check)
|
||||
Set<BlockNode> paths = BlockUtils.getAllPathsBlocks(mth.getEnterBlock(), exit);
|
||||
@@ -406,7 +413,7 @@ public class RegionMaker {
|
||||
return false;
|
||||
}
|
||||
InsnNode breakInsn = new InsnNode(InsnType.BREAK, 0);
|
||||
insertBlock.getInstructions().add(breakInsn);
|
||||
EdgeInsnAttr.addEdgeInsn(insertBlock, insertBlock.getSuccessors().get(0), breakInsn);
|
||||
stack.addExit(exit);
|
||||
// add label to 'break' if needed
|
||||
addBreakLabel(exitEdge, exit, breakInsn);
|
||||
@@ -499,18 +506,21 @@ public class RegionMaker {
|
||||
return true;
|
||||
}
|
||||
|
||||
private final Set<BlockNode> cacheSet = new HashSet<BlockNode>();
|
||||
|
||||
private BlockNode processMonitorEnter(IRegion curRegion, BlockNode block, InsnNode insn, RegionStack stack) {
|
||||
SynchronizedRegion synchRegion = new SynchronizedRegion(curRegion, insn);
|
||||
synchRegion.getSubBlocks().add(block);
|
||||
curRegion.getSubBlocks().add(synchRegion);
|
||||
|
||||
Set<BlockNode> exits = new HashSet<BlockNode>();
|
||||
cacheSet.clear();
|
||||
Set<BlockNode> cacheSet = new HashSet<BlockNode>();
|
||||
traverseMonitorExits(synchRegion, insn.getArg(0), block, exits, cacheSet);
|
||||
|
||||
for (InsnNode exitInsn : synchRegion.getExitInsns()) {
|
||||
BlockNode insnBlock = BlockUtils.getBlockByInsn(mth, exitInsn);
|
||||
if (insnBlock != null) {
|
||||
insnBlock.add(AFlag.SKIP);
|
||||
}
|
||||
exitInsn.add(AFlag.SKIP);
|
||||
InstructionRemover.unbindInsn(mth, exitInsn);
|
||||
}
|
||||
|
||||
@@ -519,16 +529,26 @@ public class RegionMaker {
|
||||
ErrorsCounter.methodError(mth, "Unexpected end of synchronized block");
|
||||
return null;
|
||||
}
|
||||
BlockNode exit;
|
||||
BlockNode exit = null;
|
||||
if (exits.size() == 1) {
|
||||
exit = getNextBlock(exits.iterator().next());
|
||||
} else {
|
||||
} else if (exits.size() > 1) {
|
||||
cacheSet.clear();
|
||||
exit = traverseMonitorExitsCross(body, exits, cacheSet);
|
||||
}
|
||||
|
||||
stack.push(synchRegion);
|
||||
stack.addExit(exit);
|
||||
if (exit != null) {
|
||||
stack.addExit(exit);
|
||||
} else {
|
||||
for (BlockNode exitBlock : exits) {
|
||||
// don't add exit blocks which leads to method end blocks ('return', 'throw', etc)
|
||||
List<BlockNode> list = BlockUtils.buildSimplePath(exitBlock);
|
||||
if (list.isEmpty() || !list.get(list.size() - 1).getSuccessors().isEmpty()) {
|
||||
stack.addExit(exitBlock);
|
||||
}
|
||||
}
|
||||
}
|
||||
synchRegion.getSubBlocks().add(makeRegion(body, stack));
|
||||
stack.pop();
|
||||
return exit;
|
||||
@@ -616,8 +636,9 @@ public class RegionMaker {
|
||||
ifRegion.setCondition(currentIf.getCondition());
|
||||
currentRegion.getSubBlocks().add(ifRegion);
|
||||
|
||||
BlockNode outBlock = currentIf.getOutBlock();
|
||||
stack.push(ifRegion);
|
||||
stack.addExit(currentIf.getOutBlock());
|
||||
stack.addExit(outBlock);
|
||||
|
||||
ifRegion.setThenRegion(makeRegion(currentIf.getThenBlock(), stack));
|
||||
BlockNode elseBlock = currentIf.getElseBlock();
|
||||
@@ -627,8 +648,41 @@ public class RegionMaker {
|
||||
ifRegion.setElseRegion(makeRegion(elseBlock, stack));
|
||||
}
|
||||
|
||||
// insert edge insns in new 'else' branch
|
||||
// TODO: make more common algorithm
|
||||
if (ifRegion.getElseRegion() == null && outBlock != null) {
|
||||
List<EdgeInsnAttr> edgeInsnAttrs = outBlock.getAll(AType.EDGE_INSN);
|
||||
if (!edgeInsnAttrs.isEmpty()) {
|
||||
Region elseRegion = new Region(ifRegion);
|
||||
for (EdgeInsnAttr edgeInsnAttr : edgeInsnAttrs) {
|
||||
if (edgeInsnAttr.getEnd().equals(outBlock)) {
|
||||
addEdgeInsn(currentIf, elseRegion, edgeInsnAttr);
|
||||
}
|
||||
}
|
||||
ifRegion.setElseRegion(elseRegion);
|
||||
}
|
||||
}
|
||||
|
||||
stack.pop();
|
||||
return currentIf.getOutBlock();
|
||||
return outBlock;
|
||||
}
|
||||
|
||||
private void addEdgeInsn(IfInfo ifInfo, Region region, EdgeInsnAttr edgeInsnAttr) {
|
||||
BlockNode start = edgeInsnAttr.getStart();
|
||||
if (start.contains(AFlag.SKIP)) {
|
||||
return;
|
||||
}
|
||||
boolean fromThisIf = false;
|
||||
for (BlockNode ifBlock : ifInfo.getMergedBlocks()) {
|
||||
if (ifBlock.getSuccessors().contains(start)) {
|
||||
fromThisIf = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!fromThisIf) {
|
||||
return;
|
||||
}
|
||||
region.add(start);
|
||||
}
|
||||
|
||||
private BlockNode processSwitch(IRegion currentRegion, BlockNode block, SwitchNode insn, RegionStack stack) {
|
||||
@@ -652,7 +706,9 @@ public class RegionMaker {
|
||||
Map<BlockNode, List<Object>> blocksMap = new LinkedHashMap<BlockNode, List<Object>>(len);
|
||||
for (Map.Entry<Integer, List<Object>> entry : casesMap.entrySet()) {
|
||||
BlockNode c = getBlockByOffset(entry.getKey(), block.getSuccessors());
|
||||
assert c != null;
|
||||
if (c == null) {
|
||||
throw new JadxRuntimeException("Switch block not found by offset: " + entry.getKey());
|
||||
}
|
||||
blocksMap.put(c, entry.getValue());
|
||||
}
|
||||
BlockNode defCase = getBlockByOffset(insn.getDefaultCaseOffset(), block.getSuccessors());
|
||||
@@ -845,7 +901,7 @@ public class RegionMaker {
|
||||
}
|
||||
}
|
||||
|
||||
public void processTryCatchBlocks(MethodNode mth) {
|
||||
public IRegion processTryCatchBlocks(MethodNode mth) {
|
||||
Set<TryCatchBlock> tcs = new HashSet<TryCatchBlock>();
|
||||
for (ExceptionHandler handler : mth.getExceptionHandlers()) {
|
||||
tcs.add(handler.getTryBlock());
|
||||
@@ -881,6 +937,40 @@ public class RegionMaker {
|
||||
processExcHandler(handler, exits);
|
||||
}
|
||||
}
|
||||
return processHandlersOutBlocks(mth, tcs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search handlers successor blocks not included in any region.
|
||||
*/
|
||||
protected IRegion processHandlersOutBlocks(MethodNode mth, Set<TryCatchBlock> tcs) {
|
||||
Set<IBlock> allRegionBlocks = new HashSet<IBlock>();
|
||||
RegionUtils.getAllRegionBlocks(mth.getRegion(), allRegionBlocks);
|
||||
|
||||
Set<IBlock> succBlocks = new HashSet<IBlock>();
|
||||
for (TryCatchBlock tc : tcs) {
|
||||
for (ExceptionHandler handler : tc.getHandlers()) {
|
||||
IContainer region = handler.getHandlerRegion();
|
||||
if (region != null) {
|
||||
IBlock lastBlock = RegionUtils.getLastBlock(region);
|
||||
if (lastBlock instanceof BlockNode) {
|
||||
succBlocks.addAll(((BlockNode) lastBlock).getSuccessors());
|
||||
}
|
||||
RegionUtils.getAllRegionBlocks(region, allRegionBlocks);
|
||||
}
|
||||
}
|
||||
}
|
||||
succBlocks.removeAll(allRegionBlocks);
|
||||
if (succBlocks.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
Region excOutRegion = new Region(mth.getRegion());
|
||||
for (IBlock block : succBlocks) {
|
||||
if (block instanceof BlockNode) {
|
||||
excOutRegion.add(makeRegion((BlockNode) block, new RegionStack(mth)));
|
||||
}
|
||||
}
|
||||
return excOutRegion;
|
||||
}
|
||||
|
||||
private void processExcHandler(ExceptionHandler handler, Set<BlockNode> exits) {
|
||||
@@ -926,7 +1016,7 @@ public class RegionMaker {
|
||||
if (b1 == null || b2 == null) {
|
||||
return false;
|
||||
}
|
||||
return isReturnBlocks(b1, b2) || isSyntheticPath(b1, b2);
|
||||
return isEqualReturnBlocks(b1, b2) || isSyntheticPath(b1, b2);
|
||||
}
|
||||
|
||||
private static boolean isSyntheticPath(BlockNode b1, BlockNode b2) {
|
||||
@@ -935,7 +1025,7 @@ public class RegionMaker {
|
||||
return (n1 != b1 || n2 != b2) && isEqualPaths(n1, n2);
|
||||
}
|
||||
|
||||
public static boolean isReturnBlocks(BlockNode b1, BlockNode b2) {
|
||||
public static boolean isEqualReturnBlocks(BlockNode b1, BlockNode b2) {
|
||||
if (!b1.isReturnBlock() || !b2.isReturnBlock()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package jadx.core.dex.visitors.regions;
|
||||
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.nodes.EdgeInsnAttr;
|
||||
import jadx.core.dex.instructions.InsnType;
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.dex.nodes.IBlock;
|
||||
@@ -19,6 +21,7 @@ import jadx.core.utils.RegionUtils;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
@@ -46,7 +49,10 @@ public class RegionMakerVisitor extends AbstractVisitor {
|
||||
mth.setRegion(rm.makeRegion(mth.getEnterBlock(), state));
|
||||
|
||||
if (!mth.isNoExceptionHandlers()) {
|
||||
rm.processTryCatchBlocks(mth);
|
||||
IRegion expOutBlock = rm.processTryCatchBlocks(mth);
|
||||
if (expOutBlock != null) {
|
||||
mth.getRegion().add(expOutBlock);
|
||||
}
|
||||
}
|
||||
|
||||
postProcessRegions(mth);
|
||||
@@ -75,10 +81,33 @@ public class RegionMakerVisitor extends AbstractVisitor {
|
||||
} else if (region instanceof SwitchRegion) {
|
||||
// insert 'break' in switch cases (run after try/catch insertion)
|
||||
processSwitch(mth, (SwitchRegion) region);
|
||||
} else if (region instanceof Region) {
|
||||
insertEdgeInsn((Region) region);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert insn block from edge insn attribute.
|
||||
*/
|
||||
private static void insertEdgeInsn(Region region) {
|
||||
List<IContainer> subBlocks = region.getSubBlocks();
|
||||
if (subBlocks.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
IContainer last = subBlocks.get(subBlocks.size() - 1);
|
||||
List<EdgeInsnAttr> edgeInsnAttrs = last.getAll(AType.EDGE_INSN);
|
||||
if (edgeInsnAttrs.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
EdgeInsnAttr insnAttr = edgeInsnAttrs.get(0);
|
||||
if (!insnAttr.getStart().equals(last)) {
|
||||
return;
|
||||
}
|
||||
List<InsnNode> insns = Collections.singletonList(insnAttr.getInsn());
|
||||
region.add(new InsnContainer(insns));
|
||||
}
|
||||
|
||||
private static void processSwitch(MethodNode mth, SwitchRegion sw) {
|
||||
for (IContainer c : sw.getBranches()) {
|
||||
if (!(c instanceof Region)) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user