Compare commits

...

73 Commits

Author SHA1 Message Date
Skylot 913a5b5d0f v0.6.1 2016-12-05 11:06:17 +03:00
Skylot c594137c19 build: remove sonar plugin from gradle config (fix #140) 2016-12-05 10:48:56 +03:00
Skylot b2f41e95bf core: export as android gradle project 2016-03-27 15:28:06 +03:00
Skylot e733c91783 gui: support images view/unpack 2016-03-26 17:19:54 +03:00
Skylot 4e982722a5 core: fix incorrect package for R class (#99) 2016-03-19 22:55:57 +03:00
Skylot 2b1f815c58 core: refactor streams closing 2016-03-19 19:14:24 +03:00
Skylot 0fff1a6754 core: fix warning from dx library 2016-03-19 18:21:52 +03:00
Skylot d95d268ec5 core: test enum implementing interface 2016-03-19 16:21:32 +03:00
Skylot b4930bc40c gui: fix issues in search dialog 2016-03-19 16:19:08 +03:00
Skylot 5f302238ad core: allow to disable constant dereference (#106) 2016-03-13 12:43:24 +03:00
Skylot 7cba2c3f81 gui: remove suffix tree search cache 2016-03-08 15:00:19 +03:00
Skylot 218c39b1ec core: option for control escaping of unicode characters (#103) 2016-03-07 19:25:57 +03:00
Skylot e915f4fcd7 core: show missing class references only once 2016-01-31 15:20:07 +03:00
Skylot bc9164b952 core: refactor file loading, add 'aar' support (fix #95) 2015-12-26 19:16:05 +03:00
Skylot 7c34be267f res: fix escape for apostrophes and quotes in string resources 2015-11-15 16:20:57 +03:00
skylot 042464438c Merge pull request #100 from netmaxt3r/master
multidex support for apk & zip
2015-11-15 16:11:32 +03:00
Nizam Moidu cf68e4722a multidex support for apk & zip 2015-11-11 12:22:47 +04:00
Skylot 7be37ff76e update gradle to 2.7 2015-10-10 14:32:10 +03:00
Skylot 1118236075 test: added module for check recompilation of test app 2015-10-10 14:26:23 +03:00
Skylot ef8a685621 resources: initial version of .arsc file decode 2015-10-09 21:41:38 +03:00
Skylot e4fef402c9 resources: don't check type chunk header size (fix #89) 2015-09-25 22:58:54 +03:00
Skylot 5528afa404 core: fix type inference for filled array (#87) 2015-09-23 22:34:32 +03:00
Skylot e3189fae37 gui: add deobfuscation button to menu 2015-09-23 22:31:38 +03:00
Skylot 6d963b378c gui: fix results render issues is search dialog 2015-09-23 21:57:32 +03:00
Skylot 895ddfa38f gui: cache renderer results in find/usage dialogs 2015-09-19 20:11:04 +03:00
Skylot 28e334a0ba gui: fix code cell renderer in find/usage dialogs 2015-09-19 20:10:43 +03:00
Skylot d060f5b877 gui: scroll to node when sync with editor 2015-09-19 20:10:09 +03:00
Skylot 7b70d617e0 core: fix variables inline (#86) 2015-09-19 16:31:08 +03:00
Skylot 261ba4645d resources: support text chuck in binary xml (fix #84) 2015-09-16 21:23:55 +03:00
Skylot 2ab7524e71 core: better args class 2015-09-08 21:29:41 +03:00
Skylot d55969bc65 core: fix some 'try/catch/finally' cases 2015-09-05 20:55:37 +03:00
Skylot 9976894091 core: skip decoding for plain text xml (fix #82) 2015-08-29 15:50:42 +03:00
skylot 76a0608a04 Merge pull request #83 from vbauer/fix-warnings
Fix console warnings during compilation (gradle build)
2015-08-29 13:28:56 +03:00
Vladislav Bauer 0d93d335a1 Fix console warnings during compilation (gradle build) 2015-08-28 20:15:51 +06:00
skylot ffb9788047 Merge pull request #81 from NeoSpb/fix_deobf
fix deobfuscation
2015-08-15 20:20:03 +03:00
NeoSpb 5dd82eede9 core: fix deobfuscation when class is in the root package (package path is empty) 2015-08-14 16:15:10 +03:00
Skylot 14b90466ef gui: restore last window position and size 2015-08-10 21:54:20 +03:00
Skylot 43592c3e49 gui: improve memory usage (#79)
- don't use suffix tree in search
- decrease default working threads count (only 1 for background jobs)
- use string refs for store only one code string without duplicates
- use cache for creating UI nodes
- allow to disable autostart for background jobs (decompilation and index)
2015-08-09 12:29:33 +03:00
Skylot b46093b3cc core: add method info cache 2015-08-09 12:12:17 +03:00
Skylot 2b9c092705 core: fix field initialization extract from try/catch block (fix #78) 2015-08-01 21:57:30 +03:00
Skylot bc73010d4e gui: add find usage feature, run decompilation and index jobs in background (#74, #75) 2015-07-26 18:06:26 +03:00
Skylot 2d8d416483 core: add cache for JavaNodes, fix definition annotations 2015-07-26 17:19:08 +03:00
skylot f549a0691e Merge pull request #76 from jpstotz/master
Enable file drop operation for loading it.
2015-07-22 16:36:07 +03:00
Jan Peter Stotz 96c2fb6f54 Enable file drop operation for loading it. 2015-07-22 14:57:28 +02:00
Skylot f6d475292c gui: add key shortcuts for menu actions. 2015-07-14 19:38:14 +03:00
Skylot bd4d4f49ff gui: add full text search (#74) 2015-07-13 22:26:26 +03:00
Skylot 5a24eac375 core: fix exit node search for synchronized block (fix #72) 2015-07-04 15:20:15 +03:00
Skylot a684118dbb core: move field initialization from constructors if possible (#71) 2015-07-01 23:01:54 +03:00
Skylot a324376e60 core: replace assertions with jadx exceptions throw 2015-06-27 21:15:57 +03:00
Skylot 04e50afaba core: fix synthetic method inline (fix #71) 2015-06-27 18:27:43 +03:00
Skylot 69494c9212 core: add method for copy instruction nodes 2015-06-27 18:27:38 +03:00
Skylot b2f0f02541 core: fix incorrectly removed 'return' in 'switch' block (fix #70) 2015-06-26 21:30:51 +03:00
Skylot 71f249113d core: allow to skip sub-blocks for region visitor. 2015-06-26 21:26:08 +03:00
Skylot 1d84c00161 core: fix 'break' in complex 'if' in loop (fix #67) 2015-06-14 15:57:37 +03:00
Skylot 5bc7e19a28 core: don't show rename comment if class name not changed 2015-06-04 20:50:25 +03:00
Skylot c46703a05d gui: run jadx-gui without console 2015-05-31 17:14:55 +03:00
Skylot 129a7c39af gui: add log viewer 2015-05-31 17:11:46 +03:00
Skylot ac3f3e8385 gui: add common popup actions for text fields. 2015-05-31 16:14:34 +03:00
skylot bc8ad4df86 Merge pull request #64 from NeoSpb/fix_deobfuscator
Fix deobfuscator
2015-05-25 20:11:00 +03:00
NeoSpb 53ac3ec582 core: fix deobfuscation for overridden methods (make identical name ('mo{index}')
for overridden methods, older 'jobf' file must be removed)
2015-05-18 21:03:53 +03:00
NeoSpb d2d43711c2 Make optional using source file name as alias for class name (some obfuscator
set the source file property with wrong value and break deobfuscation)
default: disabled
2015-05-18 21:03:51 +03:00
NeoSpb 510035b7b7 core: fix used name/path to the deobfuscation map file
(used the same name/path as the APK file, but extension 'jobf')
2015-05-18 21:03:50 +03:00
skylot c923d19bcc Merge pull request #63 from jpstotz/master
Make jadx-gui.jar runnable
2015-05-16 13:07:18 +03:00
Jan Peter Stotz bff9597360 Add Main-Class and Class-Path attributes to MANIFEST.MF of jadx-gui jar file. 2015-05-12 10:52:43 +02:00
Skylot 78b39a60e8 core: fixed invoke arguments list (fix #61) 2015-05-11 20:33:16 +03:00
Skylot 932966b6b8 core: skip synthetic arguments in anonymous class constructor 2015-05-02 20:53:22 +03:00
Skylot 85a18e6d75 core: don't insert break in method exit blocks (fix #60) 2015-05-02 20:29:15 +03:00
Skylot 5d86bf9788 core: fix loop processing after exception handler remove (fix #59) 2015-05-02 17:51:15 +03:00
Skylot 406d9878d8 core: fix invoke args skipping 2015-04-26 15:03:23 +03:00
Skylot 4e6c5cb27a core: inline anonymous classes with arguments 2015-04-25 21:40:03 +03:00
Skylot a9c0185bf5 core: fix type resolver in 'if' 2015-04-18 19:12:06 +03:00
Skylot 0111172a03 travis: tune cache options 2015-04-10 22:26:57 +03:00
Skylot 57541488d3 version 0.6.1 bump 2015-04-10 22:25:18 +03:00
220 changed files with 8580 additions and 1694 deletions
+3
View File
@@ -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
View File
@@ -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:
+36 -4
View File
@@ -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/)
+18 -14
View File
@@ -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
View File
@@ -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
View File
@@ -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;
}
}
+5 -2
View File
@@ -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;
}
}
+1
View File
@@ -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();
}
}
+81 -24
View File
@@ -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;
}
+3 -5
View File
@@ -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
*/
@@ -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