Compare commits

...

68 Commits

Author SHA1 Message Date
skylot 8c7140d6b8 fix: change not allowed access modifiers for methods (#387) (PR #439)
Fix visibility access modifies for methods (see discussions in #370 and #387):
    * all virtual methods become public
    * direct methods become private (instead constructors and static methods for now)
    * such modifications perform by default and can be disabled by the option in preferences (`--respect-bytecode-access-modifiers` in jadx-cli)
    * if changed to method added comment (`Access modifiers changed, original: private`)
2019-02-11 14:56:03 +03:00
Jan S bf42b97580 build: compile for Java 8; enable G1GC on Java 8 (PR #436) 2019-01-23 17:34:03 +03:00
skylot f8c0449d4e feat(gui): add icons to jadx-gui (#420) (PR #428) 2019-01-23 11:00:24 +03:00
Skylot b28eaa1a94 fix(gui): add synchronization to SimpleIndex class (#435) 2019-01-23 10:06:13 +03:00
Skylot be509c7104 fix(gui): use editor font in search node column 2019-01-23 10:05:00 +03:00
Skylot 2931617202 fix(gui): use editor font in files tree and fix bundled font loading 2019-01-23 09:39:57 +03:00
Skylot 82d0d622a8 fix: refactor, improve performance and fix some issues in resource processing
fix(gui): instead gradle export was executed normal export
fix(gui): content of some resource files was not shown
perf: direct resource files saving without full length buffer in memory
perf(gui): line numbers will be disabled on big files due to performance issue
feat(gui): click on HeapUsageBar will run GC and update memory info
feat(gui): add more file types for syntax highlights
refactor: ResContainer class changed for support more types of data (added link to resource file)
2019-01-22 18:51:09 +03:00
Skylot bcaca781b1 style(gui): reformat code and fix some warnings 2019-01-21 13:47:05 +03:00
Jan S ffedaea505 fix(gui): limit the spare memory to max. 512MiB (#434) 2019-01-21 09:52:00 +03:00
Skylot aec986447e fix: support multi-exception catch blocks (#421) 2019-01-19 16:28:25 +03:00
Skylot b0e3cfedf4 fix: update apksig library to latest version (#431) 2019-01-19 09:49:20 +03:00
Skylot da41efa3db fix: force rename by checks from RenameVisitor (#432) 2019-01-18 16:50:11 +03:00
Jan S 9e0cd2e14e fix(gui): add synchronizations to search index creation (#433)
* fix: unsynchronized search index creation (code usage) results in ArrayIndexOutOfBoundsException and stuck at 99%

* fix: use computeIfAbsent instead of synchronized block
2019-01-18 16:47:44 +03:00
Jan S d1af751226 feat(gui): APK signature check v1/v2 using the apksig library from Google (#431)
* feat: APK signature check v1/v2 using the apksig library from Google
* fix: proposed changes implemented
2019-01-18 12:26:22 +03:00
Skylot 618b014b3d fix: rename method wrapped by synthetic only from same class (#430) 2019-01-16 22:27:50 +03:00
Jan S 7c353a6c6f fix(gui): unsynchronized search index creation results in NullPointerException upon performing search (#429) 2019-01-15 13:05:45 +03:00
Jan S 72b2663949 fix: ArrayIndexOutOfBoundsException in string concatenation visitor (#427)
* fix: ArrayIndexOutOfBoundsException in string concatenation visitor
* fix: typo in comment
* fix: StringBuilder chain processing created wrong code
* test: simple JUnit test cases added for testing StringBuilder chain processing (chains that can be and that can't be simplified)
2019-01-12 21:12:28 +03:00
Skylot 727285e3df chore: update dependencies and gradle 2019-01-12 19:07:37 +03:00
Skylot a932c7c569 build: add java 11 to build on travis 2019-01-12 13:06:41 +03:00
Skylot 1272ae2d4d fix(gui): don't skip indexing code lines starting with '}' (#426) 2019-01-10 23:46:59 +03:00
Skylot ddaf0375dc docs: add pyjadx link in readme (#424) 2019-01-07 11:28:03 +03:00
Jan S f60bb6b121 fix: various UI improvements (#419)
* fixed wait time for background jobs
* enable multi-threaded decompiling
* added preference for excluding certain packages from decompiling and indexing
* show message dialog in case classes are not indexed because of low memory
* added heap usage bar for visualizing Java memory usage
2019-01-06 15:46:54 +03:00
Donlon fd3498add6 fix: show method alias in "method not decompiled" messages (#410) 2019-01-06 14:02:37 +03:00
Jan Peter Stotz 1ac2cdfc41 fix: wait time for background jobs too short 2018-12-26 20:21:16 +03:00
Skylot eadf046b2c chore: show try/catch processing problems in code comments 2018-12-25 17:29:36 +03:00
Skylot e9591efd7e fix: search exception handler splitter block by offset if jump source unknown (#406) 2018-12-25 17:27:42 +03:00
Skylot fbf750f588 build: update jacoco for build with java 11 2018-12-22 15:10:09 +03:00
Skylot 63c528dba9 build: update shadowJar for build with gradle 5.0 2018-12-22 14:57:06 +03:00
Skylot 0f27eba1b1 fix: don't rename constructors and class init methods in deobfuscator (#415) 2018-12-22 13:03:06 +03:00
Skylot a841d0ebe7 fix: use '$' for inner classes also in methods and fields (#415) 2018-12-22 13:02:27 +03:00
Skylot e0624ce986 fix: use '$' as separator for inner classes in .jobf file (#415) 2018-12-21 19:44:25 +03:00
Skylot 7e8435cceb fix(gui): fill background before draw line numbers (#404) 2018-12-06 14:03:09 +03:00
Skylot 6d59f77165 fix: process try/catch without move-exception instruction (#395) 2018-11-26 14:31:49 +03:00
Marcin Kamionowski 3a798cb21c fix: return type lost after type inference (#396) 2018-11-23 20:01:50 +03:00
Skylot 1fc92d2a16 fix: instruction deep equals must check result 2018-11-16 19:04:38 +03:00
Skylot 850bd96976 fix: don't remove synthetic class with inner classes 2018-11-11 21:04:37 +03:00
Skylot 20b03aa755 fix: don't remove synthetic method if args count or name not same (#361) 2018-11-09 19:54:00 +03:00
Jan S 5281eed1a5 fix: loading of i18n resources as UTF-8 (see #363) (PR #386) 2018-11-07 22:57:31 +03:00
Parth Bhatia bedbf94b4a fix: update dx to version 16 (#369) 2018-11-07 20:10:37 +03:00
Skylot 47917fd5c2 fix: resolve some sonar critical issues 2018-10-29 22:27:28 +03:00
Skylot 0abb51c87a fix(gui): on settings reset run upgrade method 2018-10-29 22:27:05 +03:00
Skylot 557667b125 fix(gui): allow partial settings sync to not save command line options 2018-10-29 22:27:05 +03:00
Skylot 1d7bb43dfd fix: correct code line number calculation 2018-10-29 18:43:22 +03:00
Skylot 6b3e8f083c fix(gui): override settings by cmd options 2018-10-29 18:42:17 +03:00
Skylot bc629337d6 fix(gui): add "use imports" option to preferences 2018-10-29 18:35:23 +03:00
Skylot 58993b9799 fix(gui): apply render hints for line numbers 2018-10-28 18:52:43 +03:00
Skylot a3464d7184 fix(gui): make link for full class names (#378) 2018-10-28 18:52:43 +03:00
Jan S a8a31643f1 fix: Fix for #377 (Jadx in Windows open with list) (#379) 2018-10-28 18:51:25 +03:00
Jan S df9ae295db feat: make the import class name clickable (#378) 2018-10-25 16:36:37 +03:00
Skylot 8c348c935c build: disable travis build on oracle jdk 10 2018-10-24 21:45:00 +03:00
Skylot 3815d30fc1 fix: force rename fields and methods with reserved names (#364) 2018-10-24 21:30:36 +03:00
Skylot 778b9bb851 fix: resolve lint errors in resource save methods 2018-10-24 21:30:36 +03:00
Sergey Toshin 57dd9e6146 Removes useless imports which prevented gradle build 2018-10-24 21:21:17 +03:00
sergey-wowwow 8eef4a9075 fix: saves all resources (#375) 2018-10-24 20:58:49 +03:00
sergey-wowwow 87f50ab513 fix: exports resources first (#376) 2018-10-24 20:56:32 +03:00
Skylot 2de86b6db5 fix(gui): make correct size truncate for recent files list 2018-09-08 17:59:38 +03:00
Skylot 9be62fb16e fix: lower regions count limit (#354) 2018-09-08 17:45:04 +03:00
Skylot f6f883b9d1 fix: change resource fields generations in R class (#308) 2018-09-08 14:33:33 +03:00
sergey-wowwow 5de4d0792f fix: generates code of missing R class (#353) 2018-09-08 14:32:59 +03:00
Skylot 8c43e7f7ce style: fix code formating 2018-09-08 10:22:06 +03:00
Skylot 9e24a5abeb fix(gui): show 'copy name' action only for supported nodes 2018-09-08 10:12:40 +03:00
Skylot b587b6d694 fix(gui): use correct font and style for certificate panel 2018-09-08 10:12:40 +03:00
Skylot bc3af8e64d fix: resolve some sonar warnings 2018-09-08 10:12:40 +03:00
Skylot 7bd428cf6d build: fix gitlab config 2018-09-08 10:12:40 +03:00
Skylot 912f3c8467 build: skip gradle assemble before build 2018-09-01 14:27:15 +03:00
javaeryang a8febb2447 feat(gui): add a menu to copy class name (#351) 2018-08-28 11:28:53 +03:00
Skylot 1b0b526822 chore: remove 'v' from version string 2018-08-26 23:15:48 +03:00
Skylot 6250ebdd75 chore: don't use labels for artifacts in github release 2018-08-26 23:14:18 +03:00
195 changed files with 4930 additions and 2962 deletions
+1
View File
@@ -9,6 +9,7 @@ out/
*.iml
*.ipr
*.iws
.attach_pid*
**/.DS_Store
+3 -2
View File
@@ -12,9 +12,10 @@ stages:
build:
stage: build
before_script:
- export JADX_LAST_TAG="$(git describe --abbrev=0 --tags)"
- export JADX_VERSION="${JADX_LAST_TAG:1}-$(git rev-parse --short HEAD)"
script:
- sed -i " 1 s/.*/&-glb$(git rev-list --count HEAD)-$(git rev-parse --short HEAD)/" version
- cat version
- ./gradlew -g /cache/.gradle clean build jacocoTestReport
- ./gradlew -g /cache/.gradle clean sonarqube -Dsonar.host.url=$SONAR_HOST -Dsonar.organization=$SONAR_ORG -Dsonar.login=$SONAR_TOKEN
- ./gradlew -g /cache/.gradle clean dist
-2
View File
@@ -10,7 +10,5 @@ publish:
- path: '@semantic-release/github'
assets:
- path: 'build/*.zip'
label: 'zip bundle'
- path: 'build/*.exe'
label: 'jadx-gui windows'
+8 -4
View File
@@ -9,20 +9,24 @@ git:
depth: false
before_install:
- wget https://github.com/sormuras/bach/raw/master/install-jdk.sh
- chmod +x gradlew
# override install to skip 'gradle assemble'
install:
- true
env:
global:
- TERM=dumb
- JADX_VERSION="$(git describe --abbrev=0 --tags)-b$TRAVIS_BUILD_NUMBER-$(git rev-parse --short HEAD)"
- JADX_LAST_TAG=$(git describe --abbrev=0 --tags)
- JADX_VERSION="${JADX_LAST_TAG:1}-b$TRAVIS_BUILD_NUMBER-$(git rev-parse --short HEAD)"
matrix:
include:
- env: JDK=oracle-8
jdk: oraclejdk8
- env: JDK=oracle-10
install: . ./install-jdk.sh -F 10 -L BCL
- env: JDK=openjdk11
jdk: openjdk11
script:
- java -version
+7 -2
View File
@@ -25,8 +25,13 @@ After download unpack zip file go to `bin` directory and run:
On Windows run `.bat` files with double-click\
**Note:** ensure you have installed Java 8 64-bit version
### Building from source
Java 8 JDK or higher must be installed:
### Related projects:
- [PyJadx](https://github.com/romainthomas/pyjadx) - python binding for jadx by [@romainthomas](https://github.com/romainthomas)
### Building jadx from source
JDK 8 or higher must be installed:
git clone https://github.com/skylot/jadx.git
cd jadx
+5 -7
View File
@@ -1,6 +1,5 @@
plugins {
id 'com.github.ksoichiro.console.reporter' version '0.5.0'
id 'org.sonarqube' version '2.6.2'
id 'org.sonarqube' version '2.7'
id 'com.github.ben-manes.versions' version '0.20.0'
}
@@ -12,7 +11,6 @@ allprojects {
apply plugin: 'java'
apply plugin: 'groovy'
apply plugin: 'jacoco'
apply plugin: 'com.github.ksoichiro.console.reporter'
version = jadxVersion
@@ -41,20 +39,20 @@ allprojects {
testCompile 'ch.qos.logback:logback-classic:1.2.3'
testCompile 'junit:junit:4.12'
testCompile 'org.hamcrest:hamcrest-library:1.3'
testCompile 'org.mockito:mockito-core:2.20.1'
testCompile 'org.hamcrest:hamcrest-library:2.1'
testCompile 'org.mockito:mockito-core:2.23.4'
testCompile 'org.spockframework:spock-core:1.1-groovy-2.4'
testCompile 'cglib:cglib-nodep:3.2.7'
}
repositories {
mavenLocal()
mavenCentral()
jcenter()
google()
}
jacoco {
toolVersion = "0.8.1"
toolVersion = "0.8.2"
}
jacocoTestReport {
reports {
+1 -1
View File
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
@@ -0,0 +1,105 @@
package jadx.cli;
import java.io.PrintStream;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import com.beust.jcommander.JCommander;
import com.beust.jcommander.ParameterDescription;
import com.beust.jcommander.ParameterException;
import com.beust.jcommander.Parameterized;
import jadx.api.JadxDecompiler;
public class JCommanderWrapper<T> {
private final JCommander jc;
public JCommanderWrapper(T obj) {
this.jc = JCommander.newBuilder().addObject(obj).build();
}
public boolean parse(String[] args) {
try {
jc.parse(args);
return true;
} catch (ParameterException e) {
System.err.println("Arguments parse error: " + e.getMessage());
printUsage();
return false;
}
}
public void overrideProvided(T obj) {
List<ParameterDescription> fieldsParams = jc.getParameters();
List<ParameterDescription> parameters = new ArrayList<>(1 + fieldsParams.size());
parameters.add(jc.getMainParameterValue());
parameters.addAll(fieldsParams);
for (ParameterDescription parameter : parameters) {
if (parameter.isAssigned()) {
// copy assigned field value to obj
Parameterized parameterized = parameter.getParameterized();
Object val = parameterized.get(parameter.getObject());
parameterized.set(obj, val);
}
}
}
public void printUsage() {
// print usage in not sorted fields order (by default its sorted by description)
PrintStream out = System.out;
out.println();
out.println("jadx - dex to java decompiler, version: " + JadxDecompiler.getVersion());
out.println();
out.println("usage: jadx [options] " + jc.getMainParameterDescription());
out.println("options:");
List<ParameterDescription> params = jc.getParameters();
Map<String, ParameterDescription> paramsMap = new LinkedHashMap<>(params.size());
int maxNamesLen = 0;
for (ParameterDescription p : params) {
paramsMap.put(p.getParameterized().getName(), p);
int len = p.getNames().length();
if (len > maxNamesLen) {
maxNamesLen = len;
}
}
JadxCLIArgs args = new JadxCLIArgs();
Field[] fields = args.getClass().getDeclaredFields();
for (Field f : fields) {
String name = f.getName();
ParameterDescription p = paramsMap.get(name);
if (p == null) {
continue;
}
StringBuilder opt = new StringBuilder();
opt.append(" ").append(p.getNames());
addSpaces(opt, maxNamesLen - opt.length() + 3);
opt.append("- ").append(p.getDescription());
addDefaultValue(args, f, opt);
out.println(opt);
}
out.println("Example:");
out.println(" jadx -d out classes.dex");
}
private void addDefaultValue(JadxCLIArgs args, Field f, StringBuilder opt) {
Class<?> fieldType = f.getType();
if (fieldType == int.class) {
try {
int val = f.getInt(args);
opt.append(" (default: ").append(val).append(")");
} catch (Exception e) {
// ignore
}
}
}
private static void addSpaces(StringBuilder str, int count) {
for (int i = 0; i < count; i++) {
str.append(' ');
}
}
}
@@ -1,19 +1,12 @@
package jadx.cli;
import java.io.PrintStream;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.Appender;
import com.beust.jcommander.JCommander;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.ParameterDescription;
import com.beust.jcommander.ParameterException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -60,6 +53,9 @@ public class JadxCLIArgs {
@Parameter(names = {"--escape-unicode"}, description = "escape non latin characters in strings (with \\u)")
protected boolean escapeUnicode = false;
@Parameter(names = {"--respect-bytecode-access-modifiers"}, description = "don't change original access modifiers")
protected boolean respectBytecodeAccessModifiers = false;
@Parameter(names = {"--deobf"}, description = "activate deobfuscation")
protected boolean deobfuscationOn = false;
@@ -94,27 +90,26 @@ public class JadxCLIArgs {
protected boolean printHelp = false;
public boolean processArgs(String[] args) {
return parse(args) && process();
JCommanderWrapper<JadxCLIArgs> jcw = new JCommanderWrapper<>(this);
return jcw.parse(args) && process(jcw);
}
private boolean parse(String[] args) {
try {
makeJCommander().parse(args);
return true;
} catch (ParameterException e) {
System.err.println("Arguments parse error: " + e.getMessage());
printUsage();
/**
* Set values only for options provided in cmd.
* Used to merge saved options and options passed in command line.
*/
public boolean overrideProvided(String[] args) {
JCommanderWrapper<JadxCLIArgs> jcw = new JCommanderWrapper<>(new JadxCLIArgs());
if (!jcw.parse(args)) {
return false;
}
jcw.overrideProvided(this);
return process(jcw);
}
private JCommander makeJCommander() {
return JCommander.newBuilder().addObject(this).build();
}
private boolean process() {
private boolean process(JCommanderWrapper jcw) {
if (printHelp) {
printUsage();
jcw.printUsage();
return false;
}
if (printVersion) {
@@ -136,69 +131,12 @@ public class JadxCLIArgs {
}
} catch (JadxException e) {
System.err.println("ERROR: " + e.getMessage());
printUsage();
jcw.printUsage();
return false;
}
return true;
}
public void printUsage() {
JCommander jc = makeJCommander();
// print usage in not sorted fields order (by default its sorted by description)
PrintStream out = System.out;
out.println();
out.println("jadx - dex to java decompiler, version: " + JadxDecompiler.getVersion());
out.println();
out.println("usage: jadx [options] " + jc.getMainParameterDescription());
out.println("options:");
List<ParameterDescription> params = jc.getParameters();
Map<String, ParameterDescription> paramsMap = new LinkedHashMap<>(params.size());
int maxNamesLen = 0;
for (ParameterDescription p : params) {
paramsMap.put(p.getParameterized().getName(), p);
int len = p.getNames().length();
if (len > maxNamesLen) {
maxNamesLen = len;
}
}
JadxCLIArgs args = new JadxCLIArgs();
Field[] fields = args.getClass().getDeclaredFields();
for (Field f : fields) {
String name = f.getName();
ParameterDescription p = paramsMap.get(name);
if (p == null) {
continue;
}
StringBuilder opt = new StringBuilder();
opt.append(" ").append(p.getNames());
addSpaces(opt, maxNamesLen - opt.length() + 3);
opt.append("- ").append(p.getDescription());
addDefaultValue(args, f, opt);
out.println(opt);
}
out.println("Example:");
out.println(" jadx -d out classes.dex");
}
private void addDefaultValue(JadxCLIArgs args, Field f, StringBuilder opt) {
Class<?> fieldType = f.getType();
if (fieldType == int.class) {
try {
int val = f.getInt(args);
opt.append(" (default: ").append(val).append(")");
} catch (Exception e) {
// ignore
}
}
}
private static void addSpaces(StringBuilder str, int count) {
for (int i = 0; i < count; i++) {
str.append(' ');
}
}
public JadxArgs toJadxArgs() {
JadxArgs args = new JadxArgs();
args.setInputFiles(files.stream().map(FileUtils::toFile).collect(Collectors.toList()));
@@ -219,6 +157,7 @@ public class JadxCLIArgs {
args.setDeobfuscationMaxLength(deobfuscationMaxLength);
args.setUseSourceNameAsClassAlias(deobfuscationUseSourceNameAsAlias);
args.setEscapeUnicode(escapeUnicode);
args.setRespectBytecodeAccModifiers(respectBytecodeAccessModifiers);
args.setExportAsGradleProject(exportAsGradleProject);
args.setUseImports(useImports);
return args;
@@ -304,6 +243,10 @@ public class JadxCLIArgs {
return replaceConsts;
}
public boolean isRespectBytecodeAccessModifiers() {
return respectBytecodeAccessModifiers;
}
public boolean isExportAsGradleProject() {
return exportAsGradleProject;
}
@@ -30,11 +30,35 @@ public class JadxCLIArgsTest {
assertThat(parse("").isSkipSources(), is(false));
}
@Test
public void testOptionsOverride() {
assertThat(override(new JadxCLIArgs(), "--no-imports").isUseImports(), is(false));
assertThat(override(new JadxCLIArgs(), "").isUseImports(), is(true));
JadxCLIArgs args = new JadxCLIArgs();
args.useImports = false;
assertThat(override(args, "--no-imports").isUseImports(), is(false));
args = new JadxCLIArgs();
args.useImports = false;
assertThat(override(args, "").isUseImports(), is(false));
}
private JadxCLIArgs parse(String... args) {
JadxCLIArgs jadxArgs = new JadxCLIArgs();
return parse(new JadxCLIArgs(), args);
}
private JadxCLIArgs parse(JadxCLIArgs jadxArgs, String... args) {
boolean res = jadxArgs.processArgs(args);
assertThat(res, is(true));
LOG.info("Jadx args: {}", jadxArgs.toJadxArgs());
return jadxArgs;
}
private JadxCLIArgs override(JadxCLIArgs jadxArgs, String... args) {
boolean res = jadxArgs.overrideProvided(args);
assertThat(res, is(true));
LOG.info("Jadx args: {}", jadxArgs.toJadxArgs());
return jadxArgs;
}
}
+7 -7
View File
@@ -3,15 +3,15 @@ ext.jadxClasspath = 'clsp-data/android-5.1.jar'
dependencies {
runtime files(jadxClasspath)
compile files('lib/dx-1.14.jar')
compile files('lib/dx-1.16.jar')
compile 'commons-io:commons-io:2.6'
compile 'org.ow2.asm:asm:6.2'
compile 'org.jetbrains:annotations:16.0.2'
compile 'uk.com.robust-it:cloning:1.9.10'
compile 'org.ow2.asm:asm:7.0'
compile 'org.jetbrains:annotations:16.0.3'
compile 'uk.com.robust-it:cloning:1.9.11'
testCompile 'org.smali:smali:2.2.4'
testCompile 'org.smali:baksmali:2.2.4'
testCompile 'org.smali:smali:2.2.5'
testCompile 'org.smali:baksmali:2.2.5'
testCompile 'org.apache.commons:commons-lang3:3.7'
testCompile 'org.apache.commons:commons-lang3:3.8.1'
}
Binary file not shown.
Binary file not shown.
@@ -40,6 +40,7 @@ public class JadxArgs {
private boolean escapeUnicode = false;
private boolean replaceConsts = true;
private boolean respectBytecodeAccModifiers = false;
private boolean exportAsGradleProject = false;
public JadxArgs() {
@@ -204,6 +205,14 @@ public class JadxArgs {
this.replaceConsts = replaceConsts;
}
public boolean isRespectBytecodeAccModifiers() {
return respectBytecodeAccModifiers;
}
public void setRespectBytecodeAccModifiers(boolean respectBytecodeAccModifiers) {
this.respectBytecodeAccModifiers = respectBytecodeAccModifiers;
}
public boolean isExportAsGradleProject() {
return exportAsGradleProject;
}
@@ -234,8 +243,10 @@ public class JadxArgs {
sb.append(", deobfuscationMaxLength=").append(deobfuscationMaxLength);
sb.append(", escapeUnicode=").append(escapeUnicode);
sb.append(", replaceConsts=").append(replaceConsts);
sb.append(", respectBytecodeAccModifiers=").append(respectBytecodeAccModifiers);
sb.append(", exportAsGradleProject=").append(exportAsGradleProject);
sb.append('}');
return sb.toString();
}
}
@@ -42,7 +42,7 @@ import jadx.core.xmlgen.ResourcesSaver;
* jadx.load();
* jadx.save();
* </code></pre>
*
* <p>
* Instead of 'save()' you can iterate over decompiled classes:
* <pre><code>
* for(JavaClass cls : jadx.getClasses()) {
@@ -91,7 +91,6 @@ public final class JadxDecompiler {
root.initClassPath();
root.loadResources(getResources());
root.initAppResClass();
initVisitors();
}
@@ -176,12 +175,12 @@ public final class JadxDecompiler {
sourcesOutDir = args.getOutDirSrc();
resOutDir = args.getOutDirRes();
}
if (saveSources) {
appendSourcesSave(executor, sourcesOutDir);
}
if (saveResources) {
appendResourcesSave(executor, resOutDir);
}
if (saveSources) {
appendSourcesSave(executor, sourcesOutDir);
}
return executor;
}
@@ -312,10 +311,38 @@ public final class JadxDecompiler {
return methodsMap;
}
JavaMethod getJavaMethodByNode(MethodNode mth) {
JavaMethod javaMethod = methodsMap.get(mth);
if (javaMethod != null) {
return javaMethod;
}
// parent class not loaded yet
JavaClass javaClass = classesMap.get(mth.getParentClass());
if (javaClass != null) {
javaClass.decompile();
return methodsMap.get(mth);
}
return null;
}
Map<FieldNode, JavaField> getFieldsMap() {
return fieldsMap;
}
JavaField getJavaFieldByNode(FieldNode fld) {
JavaField javaField = fieldsMap.get(fld);
if (javaField != null) {
return javaField;
}
// parent class not loaded yet
JavaClass javaClass = classesMap.get(fld.getParentClass());
if (javaClass != null) {
javaClass.decompile();
return fieldsMap.get(fld);
}
return null;
}
public JadxArgs getArgs() {
return args;
}
@@ -160,10 +160,10 @@ public final class JavaClass implements JavaNode {
return getRootDecompiler().getClassesMap().get(obj);
}
if (obj instanceof MethodNode) {
return getRootDecompiler().getMethodsMap().get(obj);
return getRootDecompiler().getJavaMethodByNode(((MethodNode) obj));
}
if (obj instanceof FieldNode) {
return getRootDecompiler().getFieldsMap().get(obj);
return getRootDecompiler().getJavaFieldByNode((FieldNode) obj);
}
return null;
}
@@ -181,15 +181,6 @@ public final class JavaClass implements JavaNode {
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();
@@ -265,6 +256,6 @@ public final class JavaClass implements JavaNode {
@Override
public String toString() {
return cls.getFullName() + "[ " + getFullName() + " ]";
return getFullName();
}
}
@@ -35,6 +35,13 @@ public class ResourceFile {
private final ResourceType type;
private ZipRef zipRef;
public static ResourceFile createResourceFile(JadxDecompiler decompiler, String name, ResourceType type) {
if (!ZipSecurity.isValidZipEntryName(name)) {
return null;
}
return new ResourceFile(decompiler, name, type);
}
protected ResourceFile(JadxDecompiler decompiler, String name, ResourceType type) {
this.decompiler = decompiler;
this.name = name;
@@ -65,11 +72,4 @@ public class ResourceFile {
public String toString() {
return "ResourceFile{name='" + name + '\'' + ", type=" + type + "}";
}
public static ResourceFile createResourceFileInstance(JadxDecompiler decompiler, String name, ResourceType type) {
if(!ZipSecurity.isValidZipEntryName(name)) {
return null;
}
return new ResourceFile(decompiler, name, type);
}
}
@@ -1,27 +1,18 @@
package jadx.api;
import jadx.core.codegen.CodeWriter;
import jadx.core.utils.files.ZipSecurity;
import jadx.core.xmlgen.ResContainer;
public class ResourceFileContent extends ResourceFile {
private final CodeWriter content;
private ResourceFileContent(String name, ResourceType type, 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);
}
public static ResourceFileContent createResourceFileContentInstance(String name, ResourceType type, CodeWriter content) {
if(!ZipSecurity.isValidZipEntryName(name)) {
return null;
}
return new ResourceFileContent(name, type, content);
return ResContainer.textResource(getName(), content);
}
}
@@ -30,21 +30,4 @@ public enum ResourceType {
}
return UNKNOWN;
}
public static boolean isSupportedForUnpack(ResourceType type) {
switch (type) {
case CODE:
case LIB:
case FONT:
case UNKNOWN:
return false;
case MANIFEST:
case XML:
case ARSC:
case IMG:
return true;
}
return false;
}
}
@@ -18,6 +18,7 @@ import org.slf4j.LoggerFactory;
import jadx.api.ResourceFile.ZipRef;
import jadx.core.codegen.CodeWriter;
import jadx.core.utils.Utils;
import jadx.core.utils.android.Res9patchStreamDecoder;
import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.files.InputFile;
import jadx.core.utils.files.ZipSecurity;
@@ -31,8 +32,6 @@ import static jadx.core.utils.files.FileUtils.copyStream;
public final class ResourcesLoader {
private static final Logger LOG = LoggerFactory.getLogger(ResourcesLoader.class);
private static final int LOAD_SIZE_LIMIT = 10 * 1024 * 1024;
private final JadxDecompiler jadxRef;
ResourcesLoader(JadxDecompiler jadxRef) {
@@ -47,11 +46,11 @@ public final class ResourcesLoader {
return list;
}
public interface ResourceDecoder {
ResContainer decode(long size, InputStream is) throws IOException;
public interface ResourceDecoder<T> {
T decode(long size, InputStream is) throws IOException;
}
public static ResContainer decodeStream(ResourceFile rf, ResourceDecoder decoder) throws JadxException {
public static <T> T decodeStream(ResourceFile rf, ResourceDecoder<T> decoder) throws JadxException {
try {
ZipRef zipRef = rf.getZipRef();
if (zipRef == null) {
@@ -80,40 +79,50 @@ public final class ResourcesLoader {
static ResContainer loadContent(JadxDecompiler jadxRef, ResourceFile rf) {
try {
return decodeStream(rf, (size, is) -> loadContent(jadxRef, rf, is, size));
return decodeStream(rf, (size, is) -> loadContent(jadxRef, rf, is));
} catch (JadxException e) {
LOG.error("Decode error", e);
CodeWriter cw = new CodeWriter();
cw.add("Error decode ").add(rf.getType().toString().toLowerCase());
cw.startLine(Utils.getStackTrace(e.getCause()));
return ResContainer.singleFile(rf.getName(), cw);
return ResContainer.textResource(rf.getName(), cw);
}
}
private static ResContainer loadContent(JadxDecompiler jadxRef, ResourceFile rf,
InputStream inputStream, long size) throws IOException {
InputStream inputStream) throws IOException {
switch (rf.getType()) {
case MANIFEST:
case XML:
return ResContainer.singleFile(rf.getName(),
jadxRef.getXmlParser().parse(inputStream));
CodeWriter content = jadxRef.getXmlParser().parse(inputStream);
return ResContainer.textResource(rf.getName(), content);
case ARSC:
return new ResTableParser()
.decodeFiles(inputStream);
return new ResTableParser().decodeFiles(inputStream);
case IMG:
return ResContainer.singleImageFile(rf.getName(), inputStream);
return decodeImage(rf, inputStream);
default:
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));
return ResContainer.resourceFileLink(rf);
}
}
private static ResContainer decodeImage(ResourceFile rf, InputStream inputStream) {
String name = rf.getName();
if (name.endsWith(".9.png")) {
Res9patchStreamDecoder decoder = new Res9patchStreamDecoder();
ByteArrayOutputStream os = new ByteArrayOutputStream();
try {
decoder.decode(inputStream, os);
return ResContainer.decodedData(rf.getName(), os.toByteArray());
} catch (Exception e) {
LOG.error("Failed to decode 9-patch png image, path: {}", name, e);
}
}
return ResContainer.resourceFileLink(rf);
}
private void loadFile(List<ResourceFile> list, File file) {
if (file == null) {
return;
@@ -135,7 +144,7 @@ public final class ResourcesLoader {
private void addResourceFile(List<ResourceFile> list, File file) {
String name = file.getAbsolutePath();
ResourceType type = ResourceType.getFileType(name);
ResourceFile rf = ResourceFile.createResourceFileInstance(jadxRef, name, type);
ResourceFile rf = ResourceFile.createResourceFile(jadxRef, name, type);
if (rf != null) {
list.add(rf);
}
@@ -147,7 +156,7 @@ public final class ResourcesLoader {
}
String name = entry.getName();
ResourceType type = ResourceType.getFileType(name);
ResourceFile rf = ResourceFile.createResourceFileInstance(jadxRef, name, type);
ResourceFile rf = ResourceFile.createResourceFile(jadxRef, name, type);
if (rf != null) {
rf.setZipRef(new ZipRef(zipFile, name));
list.add(rf);
@@ -19,6 +19,7 @@ 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.FixAccessModifiers;
import jadx.core.dex.visitors.IDexTreeVisitor;
import jadx.core.dex.visitors.MethodInlineVisitor;
import jadx.core.dex.visitors.ModVisitor;
@@ -96,6 +97,7 @@ public class Jadx {
passes.add(new MethodInlineVisitor());
passes.add(new ExtractFieldInit());
passes.add(new FixAccessModifiers());
passes.add(new ClassModifier());
passes.add(new EnumVisitor());
passes.add(new PrepareForCodeGen());
@@ -1,7 +1,6 @@
package jadx.core.codegen;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
@@ -34,6 +33,7 @@ import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.parser.FieldInitAttr;
import jadx.core.dex.nodes.parser.FieldInitAttr.InitType;
import jadx.core.utils.CodegenUtils;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.CodegenException;
@@ -83,18 +83,18 @@ public class ClassGen {
}
int importsCount = imports.size();
if (importsCount != 0) {
List<String> sortImports = new ArrayList<>(importsCount);
for (ClassInfo ic : imports) {
sortImports.add(ic.getAlias().getFullName());
}
Collections.sort(sortImports);
for (String imp : sortImports) {
clsCode.startLine("import ").add(imp).add(';');
}
List<ClassInfo> sortedImports = new ArrayList<>(imports);
sortedImports.sort(Comparator.comparing(classInfo -> classInfo.getAlias().getFullName()));
sortedImports.forEach(classInfo -> {
clsCode.startLine("import ");
ClassNode classNode = cls.root().resolveClass(classInfo);
if (classNode != null) {
clsCode.attachAnnotation(classNode);
}
clsCode.add(classInfo.getAlias().getFullName());
clsCode.add(';');
});
clsCode.newLine();
sortImports.clear();
imports.clear();
}
clsCode.add(clsBody);
@@ -105,9 +105,8 @@ public class ClassGen {
if (cls.contains(AFlag.DONT_GENERATE)) {
return;
}
if (cls.contains(AFlag.INCONSISTENT_CODE)) {
code.startLine("// jadx: inconsistent code");
}
CodegenUtils.addComments(code, cls);
insertDecompilationProblems(code, cls);
addClassDeclaration(code);
addClassBody(code);
}
@@ -296,14 +295,13 @@ public class ClassGen {
}
code.add(';');
} else {
CodegenUtils.addComments(code, mth);
insertDecompilationProblems(code, mth);
boolean badCode = mth.contains(AFlag.INCONSISTENT_CODE);
if (badCode) {
if (showInconsistentCode) {
code.startLine("/* Code decompiled incorrectly, please refer to instructions dump. */");
mth.remove(AFlag.INCONSISTENT_CODE);
badCode = false;
}
if (badCode && showInconsistentCode) {
code.startLine("/* Code decompiled incorrectly, please refer to instructions dump. */");
mth.remove(AFlag.INCONSISTENT_CODE);
badCode = false;
}
MethodGen mthGen;
if (badCode || mth.contains(AType.JADX_ERROR) || fallback) {
@@ -327,9 +325,9 @@ public class ClassGen {
}
}
private void insertDecompilationProblems(CodeWriter code, MethodNode mth) {
List<JadxError> errors = mth.getAll(AType.JADX_ERROR);
List<JadxWarn> warns = mth.getAll(AType.JADX_WARN);
private void insertDecompilationProblems(CodeWriter code, AttrNode node) {
List<JadxError> errors = node.getAll(AType.JADX_ERROR);
List<JadxWarn> warns = node.getAll(AType.JADX_WARN);
if (!errors.isEmpty()) {
errors.forEach(err -> {
code.startLine("/* JADX ERROR: ").add(err.getError());
@@ -343,7 +341,7 @@ public class ClassGen {
});
}
if (!warns.isEmpty()) {
warns.forEach(warn -> code.startLine("/* JADX WARNING: ").add(warn.getWarn()).add(" */"));
warns.forEach(warn -> code.startLine("/* JADX WARNING: ").addMultiLine(warn.getWarn()).add(" */"));
}
}
@@ -353,6 +351,7 @@ public class ClassGen {
if (f.contains(AFlag.DONT_GENERATE)) {
continue;
}
CodegenUtils.addComments(code, f);
annotationGen.addForField(code, f);
if (f.getFieldInfo().isRenamed()) {
@@ -289,14 +289,14 @@ public class CodeWriter {
}
public void save(File dir, String subDir, String fileName) {
if(!ZipSecurity.isValidZipEntryName(subDir) || !ZipSecurity.isValidZipEntryName(fileName)) {
if (!ZipSecurity.isValidZipEntryName(subDir) || !ZipSecurity.isValidZipEntryName(fileName)) {
return;
}
save(dir, new File(subDir, fileName).getPath());
}
public void save(File dir, String fileName) {
if(!ZipSecurity.isValidZipEntryName(fileName)) {
if (!ZipSecurity.isValidZipEntryName(fileName)) {
return;
}
save(new File(dir, fileName));
@@ -224,7 +224,7 @@ public class InsnGen {
code.add(';');
}
}
} catch (Throwable th) {
} catch (Exception th) {
throw new CodegenException(mth, "Error generate insn: " + insn, th);
}
return true;
@@ -659,7 +659,7 @@ public class InsnGen {
}
void generateMethodArguments(CodeWriter code, InsnNode insn, int startArgNum,
@Nullable MethodNode callMth) throws CodegenException {
@Nullable MethodNode callMth) throws CodegenException {
int k = startArgNum;
if (callMth != null && callMth.contains(AFlag.SKIP_FIRST_ARG)) {
k++;
@@ -4,6 +4,8 @@ import java.util.Iterator;
import java.util.List;
import com.android.dx.rop.code.AccessFlags;
import jadx.core.dex.info.ClassInfo;
import jadx.core.utils.Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -78,7 +80,7 @@ public class MethodGen {
ai = ai.remove(AccessFlags.ACC_PUBLIC);
}
if (mth.getMethodInfo().isRenamed()) {
if (mth.getMethodInfo().isRenamed() && !ai.isConstructor()) {
code.startLine("/* renamed from: ").add(mth.getName()).add(" */");
}
code.startLineWithNum(mth.getSourceLine());
@@ -87,7 +89,7 @@ public class MethodGen {
if (classGen.addGenericMap(code, mth.getGenericMap())) {
code.add(' ');
}
if (mth.getAccessFlags().isConstructor()) {
if (ai.isConstructor()) {
code.attachDefinition(mth);
code.add(classGen.getClassNode().getShortName()); // constructor
} else {
@@ -165,8 +167,16 @@ public class MethodGen {
addFallbackMethodCode(code);
code.startLine("*/");
ClassInfo clsAlias = mth.getParentClass().getAlias();
code.startLine("throw new UnsupportedOperationException(\"Method not decompiled: ")
.add(mth.toString())
.add(clsAlias.makeFullClsName(clsAlias.getShortName(), true))
.add(".")
.add(mth.getAlias())
.add("(")
.add(Utils.listToString(mth.getMethodInfo().getArgumentsTypes()))
.add("):")
.add(mth.getMethodInfo().getReturnType().toString())
.add("\");");
} else {
RegionGen regionGen = new RegionGen(this);
@@ -1,13 +1,11 @@
package jadx.core.codegen;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import jadx.core.Consts;
import jadx.core.deobf.NameMapper;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.MethodInfo;
@@ -22,6 +20,7 @@ import jadx.core.dex.instructions.mods.ConstructorInsn;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.StringUtils;
import jadx.core.utils.Utils;
public class NameGen {
@@ -32,20 +31,21 @@ public class NameGen {
private final boolean fallback;
static {
OBJ_ALIAS = new HashMap<>();
OBJ_ALIAS.put(Consts.CLASS_STRING, "str");
OBJ_ALIAS.put(Consts.CLASS_CLASS, "cls");
OBJ_ALIAS.put(Consts.CLASS_THROWABLE, "th");
OBJ_ALIAS.put(Consts.CLASS_OBJECT, "obj");
OBJ_ALIAS.put("java.util.Iterator", "it");
OBJ_ALIAS.put("java.lang.Boolean", "bool");
OBJ_ALIAS.put("java.lang.Short", "sh");
OBJ_ALIAS.put("java.lang.Integer", "num");
OBJ_ALIAS.put("java.lang.Character", "ch");
OBJ_ALIAS.put("java.lang.Byte", "b");
OBJ_ALIAS.put("java.lang.Float", "f");
OBJ_ALIAS.put("java.lang.Long", "l");
OBJ_ALIAS.put("java.lang.Double", "d");
OBJ_ALIAS = Utils.newConstStringMap(
Consts.CLASS_STRING, "str",
Consts.CLASS_CLASS, "cls",
Consts.CLASS_THROWABLE, "th",
Consts.CLASS_OBJECT, "obj",
"java.util.Iterator", "it",
"java.lang.Boolean", "bool",
"java.lang.Short", "sh",
"java.lang.Integer", "num",
"java.lang.Character", "ch",
"java.lang.Byte", "b",
"java.lang.Float", "f",
"java.lang.Long", "l",
"java.lang.Double", "d"
);
}
public NameGen(MethodNode mth, boolean fallback) {
@@ -1,5 +1,6 @@
package jadx.core.codegen;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@@ -11,6 +12,7 @@ import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.DeclareVariablesAttr;
import jadx.core.dex.attributes.nodes.ForceReturnAttr;
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.instructions.SwitchNode;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.NamedArg;
@@ -306,16 +308,23 @@ public class RegionGen extends InsnGen {
return;
}
code.startLine("} catch (");
if (handler.isCatchAll()) {
code.add("Throwable");
} else {
Iterator<ClassInfo> it = handler.getCatchTypes().iterator();
if (it.hasNext()) {
useClass(code, it.next());
}
while (it.hasNext()) {
code.add(" | ");
useClass(code, it.next());
}
}
code.add(' ');
InsnArg arg = handler.getArg();
if (arg instanceof RegisterArg) {
declareVar(code, (RegisterArg) arg);
code.add(mgen.getNameGen().assignArg((RegisterArg) arg));
} else if (arg instanceof NamedArg) {
if (handler.isCatchAll()) {
code.add("Throwable");
} else {
useClass(code, handler.getCatchType());
}
code.add(' ');
code.add(mgen.getNameGen().assignNamedArg((NamedArg) arg));
}
code.add(") {");
@@ -112,18 +112,20 @@ class DeobfPresets {
for (DeobfClsInfo deobfClsInfo : deobfuscator.getClsMap().values()) {
if (deobfClsInfo.getAlias() != null) {
list.add(String.format("c %s = %s",
deobfClsInfo.getCls().getClassInfo().getFullName(), deobfClsInfo.getAlias()));
deobfClsInfo.getCls().getClassInfo().makeRawFullName(), deobfClsInfo.getAlias()));
}
}
for (FieldInfo fld : deobfuscator.getFldMap().keySet()) {
list.add(String.format("f %s = %s", fld.getFullId(), fld.getAlias()));
list.add(String.format("f %s = %s", fld.getRawFullId(), fld.getAlias()));
}
for (MethodInfo mth : deobfuscator.getMthMap().keySet()) {
list.add(String.format("m %s = %s", mth.getFullId(), mth.getAlias()));
list.add(String.format("m %s = %s", mth.getRawFullId(), mth.getAlias()));
}
Collections.sort(list);
FileUtils.writeLines(deobfMapFile, MAP_FILE_CHARSET, list);
list.clear();
if (LOG.isDebugEnabled()) {
LOG.debug("Deobfuscation map file saved as: {}", deobfMapFile);
}
}
private static void dfsPackageName(List<String> list, String prefix, PackageNode node) {
@@ -136,15 +138,15 @@ class DeobfPresets {
}
public String getForCls(ClassInfo cls) {
return clsPresetMap.get(cls.getFullName());
return clsPresetMap.get(cls.makeRawFullName());
}
public String getForFld(FieldInfo fld) {
return fldPresetMap.get(fld.getFullId());
return fldPresetMap.get(fld.getRawFullId());
}
public String getForMth(MethodInfo mth) {
return mthPresetMap.get(mth.getFullId());
return mthPresetMap.get(mth.getRawFullId());
}
public void clear() {
@@ -129,7 +129,7 @@ public class Deobfuscator {
for (MethodInfo mth : o.getMethods()) {
if (aliasToUse == null) {
if (mth.isRenamed() && !mth.isAliasFromPreset()) {
mth.setAlias(String.format("mo%d%s", id, makeName(mth.getName())));
mth.setAlias(String.format("mo%d%s", id, prepareNamePart(mth.getName())));
}
aliasToUse = mth.getAlias();
}
@@ -243,6 +243,10 @@ public class Deobfuscator {
}
}
public void forceRenameField(FieldNode field) {
field.getFieldInfo().setAlias(makeFieldAlias(field));
}
public void renameMethod(MethodNode mth) {
String alias = getMethodAlias(mth);
if (alias != null) {
@@ -253,6 +257,13 @@ public class Deobfuscator {
}
}
public void forceRenameMethod(MethodNode mth) {
mth.getMethodInfo().setAlias(makeMethodAlias(mth));
if (mth.isVirtual()) {
resolveOverriding(mth);
}
}
public void addPackagePreset(String origPkgName, String pkgAlias) {
PackageNode pkg = getPackageNode(origPkgName, true);
pkg.setAlias(pkgAlias);
@@ -350,7 +361,7 @@ public class Deobfuscator {
if (alias == null) {
String clsName = classInfo.getShortName();
alias = String.format("C%04d%s", clsIndex++, makeName(clsName));
alias = String.format("C%04d%s", clsIndex++, prepareNamePart(clsName));
}
PackageNode pkg = getPackageNode(classInfo.getPackage(), true);
clsMap.put(classInfo, new DeobfClsInfo(this, cls, pkg, alias));
@@ -409,6 +420,9 @@ public class Deobfuscator {
@Nullable
private String getMethodAlias(MethodNode mth) {
MethodInfo methodInfo = mth.getMethodInfo();
if (methodInfo.isClassInit() || methodInfo.isConstructor()) {
return null;
}
String alias = mthMap.get(methodInfo);
if (alias != null) {
return alias;
@@ -426,13 +440,13 @@ public class Deobfuscator {
}
public String makeFieldAlias(FieldNode field) {
String alias = String.format("f%d%s", fldIndex++, makeName(field.getName()));
String alias = String.format("f%d%s", fldIndex++, prepareNamePart(field.getName()));
fldMap.put(field.getFieldInfo(), alias);
return alias;
}
public String makeMethodAlias(MethodNode mth) {
String alias = String.format("m%d%s", mthIndex++, makeName(mth.getName()));
String alias = String.format("m%d%s", mthIndex++, prepareNamePart(mth.getName()));
mthMap.put(mth.getMethodInfo(), alias);
return alias;
}
@@ -454,25 +468,21 @@ public class Deobfuscator {
String pkgName = pkg.getName();
if (!pkg.hasAlias() && shouldRename(pkgName)) {
String pkgAlias = String.format("p%03d%s", pkgIndex++, makeName(pkgName));
String pkgAlias = String.format("p%03d%s", pkgIndex++, prepareNamePart(pkgName));
pkg.setAlias(pkgAlias);
}
}
private boolean shouldRename(String s) {
return s.length() > maxLength
|| s.length() < minLength
|| NameMapper.isReserved(s)
|| !NameMapper.isAllCharsPrintable(s);
int len = s.length();
return len < minLength || len > maxLength
|| !NameMapper.isValidIdentifier(s);
}
private String makeName(String name) {
private String prepareNamePart(String name) {
if (name.length() > maxLength) {
return "x" + Integer.toHexString(name.hashCode());
}
if (NameMapper.isReserved(name)) {
return name;
}
if (!NameMapper.isAllCharsPrintable(name)) {
return removeInvalidChars(name);
}
@@ -79,12 +79,14 @@ public class NameMapper {
public static boolean isValidIdentifier(String str) {
return notEmpty(str)
&& !isReserved(str)
&& VALID_JAVA_IDENTIFIER.matcher(str).matches()
&& isAllCharsPrintable(str);
}
public static boolean isValidFullIdentifier(String str) {
return notEmpty(str)
&& !isReserved(str)
&& VALID_JAVA_FULL_IDENTIFIER.matcher(str).matches()
&& isAllCharsPrintable(str);
}
@@ -36,6 +36,7 @@ public class AType<T extends IAttribute> {
public static final AType<AttrList<JadxError>> JADX_ERROR = new AType<>();
public static final AType<AttrList<JadxWarn>> JADX_WARN = new AType<>();
public static final AType<AttrList<String>> COMMENTS = new AType<>();
public static final AType<ExcHandlerAttr> EXC_HANDLER = new AType<>();
public static final AType<CatchAttr> CATCH_BLOCK = new AType<>();
@@ -53,4 +54,7 @@ public class AType<T extends IAttribute> {
public static final AType<DeclareVariablesAttr> DECLARE_VARIABLES = new AType<>();
public static final AType<LoopLabelAttr> LOOP_LABEL = new AType<>();
public static final AType<IgnoreEdgeAttr> IGNORE_EDGE = new AType<>();
private AType() {
}
}
@@ -34,18 +34,22 @@ public final class EmptyAttrStorage extends AttributeStorage {
@Override
public void clear() {
// ignore
}
@Override
public void remove(AFlag flag) {
// ignore
}
@Override
public <T extends IAttribute> void remove(AType<T> type) {
// ignore
}
@Override
public void remove(IAttribute attr) {
// ignore
}
@Override
@@ -1,5 +1,5 @@
package jadx.core.dex.attributes;
public interface IAttribute {
AType<? extends IAttribute> getType();
<T extends IAttribute> AType<T> getType();
}
@@ -44,5 +44,4 @@ public class Annotation {
public String toString() {
return "Annotation[" + visibility + ", " + atype + ", " + values + "]";
}
}
@@ -48,5 +48,4 @@ public class AnnotationsList implements IAttribute {
public String toString() {
return Utils.listToString(map.values());
}
}
@@ -28,5 +28,4 @@ public class MethodParameters implements IAttribute {
public String toString() {
return Utils.listToString(paramList);
}
}
@@ -78,5 +78,4 @@ public class EnumClassAttr implements IAttribute {
public String toString() {
return "Enum fields: " + fields;
}
}
@@ -26,5 +26,4 @@ public class ForceReturnAttr implements IAttribute {
public String toString() {
return "FORCE_RETURN " + Utils.listToString(returnInsn.getArguments());
}
}
@@ -6,6 +6,7 @@ import jadx.core.Consts;
public class AccessInfo {
public static final int VISIBILITY_FLAGS = AccessFlags.ACC_PUBLIC | AccessFlags.ACC_PROTECTED | AccessFlags.ACC_PRIVATE;
private final int accFlags;
public enum AFType {
@@ -30,11 +31,24 @@ public class AccessInfo {
return this;
}
public AccessInfo add(int flag) {
if (!containsFlag(flag)) {
return new AccessInfo(accFlags | flag, type);
}
return this;
}
public AccessInfo changeVisibility(int flag) {
int currentVisFlags = accFlags & VISIBILITY_FLAGS;
if (currentVisFlags == flag) {
return this;
}
int unsetAllVisFlags = accFlags & ~VISIBILITY_FLAGS;
return new AccessInfo(unsetAllVisFlags | flag, type);
}
public AccessInfo getVisibility() {
int f = accFlags & AccessFlags.ACC_PUBLIC
| accFlags & AccessFlags.ACC_PROTECTED
| accFlags & AccessFlags.ACC_PRIVATE;
return new AccessInfo(f, type);
return new AccessInfo(accFlags & VISIBILITY_FLAGS, type);
}
public boolean isPublic() {
@@ -2,12 +2,14 @@ package jadx.core.dex.info;
import java.io.File;
import org.jetbrains.annotations.NotNull;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.DexNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.exceptions.JadxRuntimeException;
public final class ClassInfo {
public final class ClassInfo implements Comparable<ClassInfo> {
private final ArgType type;
private String pkg;
@@ -111,6 +113,10 @@ public final class ClassInfo {
return pkg.isEmpty() ? shortName : pkg + "." + shortName;
}
public String makeRawFullName() {
return makeFullClsName(this.name, true);
}
public String getFullPath() {
ClassInfo usedAlias = getAlias();
return usedAlias.getPackage().replace('.', File.separatorChar)
@@ -190,4 +196,9 @@ public final class ClassInfo {
}
return false;
}
@Override
public int compareTo(@NotNull ClassInfo o) {
return fullName.compareTo(o.fullName);
}
}
@@ -15,8 +15,8 @@ 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 jadx.core.utils.ErrorsCounter;
public class ConstStorage {
@@ -101,16 +101,16 @@ public class ConstStorage {
@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;
}
DexNode dex = cls.dex();
if (value instanceof Integer) {
FieldNode rField = getResourceField((Integer) value, dex);
if (rField != null) {
return rField;
}
}
boolean foundInGlobal = globalValues.contains(value);
if (foundInGlobal && !searchGlobal) {
return null;
@@ -139,6 +139,31 @@ public class ConstStorage {
return null;
}
@Nullable
private FieldNode getResourceField(Integer value, DexNode dex) {
String str = resourcesNames.get(value);
if (str == null) {
return null;
}
ClassNode appResClass = dex.root().getAppResClass();
if (appResClass == null) {
return null;
}
String[] parts = str.split("/", 2);
if (parts.length != 2) {
return null;
}
String typeName = parts[0];
String fieldName = parts[1];
for (ClassNode innerClass : appResClass.getInnerClasses()) {
if (innerClass.getShortName().equals(typeName)) {
return innerClass.searchFieldByName(fieldName);
}
}
ErrorsCounter.classWarn(appResClass, "Not found resource field with id: " + value + ", name: " + str.replace('/', '.'));
return null;
}
@Nullable
public FieldNode getConstFieldByLiteralArg(ClassNode cls, LiteralArg arg) {
PrimitiveType type = arg.getType().getPrimitiveType();
@@ -57,10 +57,18 @@ public final class FieldInfo {
return declClass.getFullName() + "." + name + ":" + TypeGen.signature(type);
}
public String getRawFullId() {
return declClass.makeRawFullName() + "." + name + ":" + TypeGen.signature(type);
}
public boolean isRenamed() {
return !name.equals(alias);
}
public boolean equalsNameAndType(FieldInfo other) {
return name.equals(other.name) && type.equals(other.type);
}
@Override
public boolean equals(Object o) {
if (this == o) {
@@ -68,6 +68,10 @@ public final class MethodInfo {
return declClass.getFullName() + "." + shortId;
}
public String getRawFullId() {
return declClass.makeRawFullName() + "." + shortId;
}
/**
* Method name and signature
*/
@@ -146,5 +150,4 @@ public final class MethodInfo {
return declClass.getFullName() + "." + name
+ "(" + Utils.listToString(args) + "):" + retType;
}
}
@@ -80,5 +80,4 @@ public class ArithNode extends InsnNode {
+ op.getSymbol() + " "
+ getArg(1);
}
}
@@ -0,0 +1,8 @@
package jadx.core.dex.instructions;
import jadx.core.dex.info.MethodInfo;
public interface CallMthInterface {
public MethodInfo getCallMth();
}
@@ -1,6 +1,5 @@
package jadx.core.dex.instructions;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.utils.InsnUtils;
public class GotoNode extends TargetInsnNode {
@@ -20,15 +19,6 @@ public class GotoNode extends TargetInsnNode {
return target;
}
@Override
public boolean replaceTargetBlock(BlockNode origin, BlockNode replace) {
return false;
}
@Override
public void initBlocks(BlockNode curBlock) {
}
@Override
public String toString() {
return super.toString() + "-> " + InsnUtils.formatOffset(target);
@@ -2,9 +2,7 @@ package jadx.core.dex.instructions;
import java.io.EOFException;
import com.android.dex.ClassData;
import com.android.dex.Code;
import com.android.dex.FieldId;
import com.android.dx.io.OpcodeInfo;
import com.android.dx.io.Opcodes;
import com.android.dx.io.instructions.DecodedInstruction;
@@ -12,9 +10,6 @@ import com.android.dx.io.instructions.FillArrayDataPayloadDecodedInstruction;
import com.android.dx.io.instructions.PackedSwitchPayloadDecodedInstruction;
import com.android.dx.io.instructions.ShortArrayCodeInput;
import com.android.dx.io.instructions.SparseSwitchPayloadDecodedInstruction;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -117,17 +112,15 @@ public class InsnDecoder {
InsnArg.lit(insn, ArgType.WIDE));
case Opcodes.CONST_STRING:
case Opcodes.CONST_STRING_JUMBO: {
InsnNode node = new ConstStringNode(dex.getString(insn.getIndex()));
node.setResult(InsnArg.reg(insn, 0, ArgType.STRING));
return node;
}
case Opcodes.CONST_STRING_JUMBO:
InsnNode constStrInsn = new ConstStringNode(dex.getString(insn.getIndex()));
constStrInsn.setResult(InsnArg.reg(insn, 0, ArgType.STRING));
return constStrInsn;
case Opcodes.CONST_CLASS: {
InsnNode node = new ConstClassNode(dex.getType(insn.getIndex()));
node.setResult(InsnArg.reg(insn, 0, ArgType.CLASS));
return node;
}
case Opcodes.CONST_CLASS:
InsnNode constClsInsn = new ConstClassNode(dex.getType(insn.getIndex()));
constClsInsn.setResult(InsnArg.reg(insn, 0, ArgType.CLASS));
return constClsInsn;
case Opcodes.MOVE:
case Opcodes.MOVE_16:
@@ -427,20 +420,18 @@ public class InsnDecoder {
null,
InsnArg.reg(insn, 0, method.getReturnType()));
case Opcodes.INSTANCE_OF: {
InsnNode node = new IndexInsnNode(InsnType.INSTANCE_OF, dex.getType(insn.getIndex()), 1);
node.setResult(InsnArg.reg(insn, 0, ArgType.BOOLEAN));
node.addArg(InsnArg.reg(insn, 1, ArgType.UNKNOWN_OBJECT));
return node;
}
case Opcodes.INSTANCE_OF:
InsnNode instInsn = new IndexInsnNode(InsnType.INSTANCE_OF, dex.getType(insn.getIndex()), 1);
instInsn.setResult(InsnArg.reg(insn, 0, ArgType.BOOLEAN));
instInsn.addArg(InsnArg.reg(insn, 1, ArgType.UNKNOWN_OBJECT));
return instInsn;
case Opcodes.CHECK_CAST: {
case Opcodes.CHECK_CAST:
ArgType castType = dex.getType(insn.getIndex());
InsnNode node = new IndexInsnNode(InsnType.CHECK_CAST, castType, 1);
node.setResult(InsnArg.reg(insn, 0, castType));
node.addArg(InsnArg.reg(insn, 0, ArgType.UNKNOWN_OBJECT));
return node;
}
InsnNode checkCastInsn = new IndexInsnNode(InsnType.CHECK_CAST, castType, 1);
checkCastInsn.setResult(InsnArg.reg(insn, 0, castType));
checkCastInsn.addArg(InsnArg.reg(insn, 0, ArgType.UNKNOWN_OBJECT));
return checkCastInsn;
case Opcodes.IGET:
case Opcodes.IGET_BOOLEAN:
@@ -448,13 +439,12 @@ public class InsnDecoder {
case Opcodes.IGET_CHAR:
case Opcodes.IGET_SHORT:
case Opcodes.IGET_WIDE:
case Opcodes.IGET_OBJECT: {
FieldInfo field = FieldInfo.fromDex(dex, insn.getIndex());
InsnNode node = new IndexInsnNode(InsnType.IGET, field, 1);
node.setResult(InsnArg.reg(insn, 0, field.getType()));
node.addArg(InsnArg.reg(insn, 1, field.getDeclClass().getType()));
return node;
}
case Opcodes.IGET_OBJECT:
FieldInfo igetFld = FieldInfo.fromDex(dex, insn.getIndex());
InsnNode igetInsn = new IndexInsnNode(InsnType.IGET, igetFld, 1);
igetInsn.setResult(InsnArg.reg(insn, 0, igetFld.getType()));
igetInsn.addArg(InsnArg.reg(insn, 1, igetFld.getDeclClass().getType()));
return igetInsn;
case Opcodes.IPUT:
case Opcodes.IPUT_BOOLEAN:
@@ -462,13 +452,12 @@ public class InsnDecoder {
case Opcodes.IPUT_CHAR:
case Opcodes.IPUT_SHORT:
case Opcodes.IPUT_WIDE:
case Opcodes.IPUT_OBJECT: {
FieldInfo field = FieldInfo.fromDex(dex, insn.getIndex());
InsnNode node = new IndexInsnNode(InsnType.IPUT, field, 2);
node.addArg(InsnArg.reg(insn, 0, field.getType()));
node.addArg(InsnArg.reg(insn, 1, field.getDeclClass().getType()));
return node;
}
case Opcodes.IPUT_OBJECT:
FieldInfo iputFld = FieldInfo.fromDex(dex, insn.getIndex());
InsnNode iputInsn = new IndexInsnNode(InsnType.IPUT, iputFld, 2);
iputInsn.addArg(InsnArg.reg(insn, 0, iputFld.getType()));
iputInsn.addArg(InsnArg.reg(insn, 1, iputFld.getDeclClass().getType()));
return iputInsn;
case Opcodes.SGET:
case Opcodes.SGET_BOOLEAN:
@@ -476,12 +465,11 @@ public class InsnDecoder {
case Opcodes.SGET_CHAR:
case Opcodes.SGET_SHORT:
case Opcodes.SGET_WIDE:
case Opcodes.SGET_OBJECT: {
FieldInfo field = FieldInfo.fromDex(dex, insn.getIndex());
InsnNode node = new IndexInsnNode(InsnType.SGET, field, 0);
node.setResult(InsnArg.reg(insn, 0, field.getType()));
return node;
}
case Opcodes.SGET_OBJECT:
FieldInfo sgetFld = FieldInfo.fromDex(dex, insn.getIndex());
InsnNode sgetInsn = new IndexInsnNode(InsnType.SGET, sgetFld, 0);
sgetInsn.setResult(InsnArg.reg(insn, 0, sgetFld.getType()));
return sgetInsn;
case Opcodes.SPUT:
case Opcodes.SPUT_BOOLEAN:
@@ -489,19 +477,17 @@ public class InsnDecoder {
case Opcodes.SPUT_CHAR:
case Opcodes.SPUT_SHORT:
case Opcodes.SPUT_WIDE:
case Opcodes.SPUT_OBJECT: {
FieldInfo field = FieldInfo.fromDex(dex, insn.getIndex());
InsnNode node = new IndexInsnNode(InsnType.SPUT, field, 1);
node.addArg(InsnArg.reg(insn, 0, field.getType()));
return node;
}
case Opcodes.SPUT_OBJECT:
FieldInfo sputFld = FieldInfo.fromDex(dex, insn.getIndex());
InsnNode sputInsn = new IndexInsnNode(InsnType.SPUT, sputFld, 1);
sputInsn.addArg(InsnArg.reg(insn, 0, sputFld.getType()));
return sputInsn;
case Opcodes.ARRAY_LENGTH: {
InsnNode node = new InsnNode(InsnType.ARRAY_LENGTH, 1);
node.setResult(InsnArg.reg(insn, 0, ArgType.INT));
node.addArg(InsnArg.reg(insn, 1, ArgType.array(ArgType.UNKNOWN)));
return node;
}
case Opcodes.ARRAY_LENGTH:
InsnNode arrLenInsn = new InsnNode(InsnType.ARRAY_LENGTH, 1);
arrLenInsn.setResult(InsnArg.reg(insn, 0, ArgType.INT));
arrLenInsn.addArg(InsnArg.reg(insn, 1, ArgType.array(ArgType.UNKNOWN)));
return arrLenInsn;
case Opcodes.AGET:
return arrayGet(insn, ArgType.NARROW);
@@ -9,7 +9,7 @@ import jadx.core.dex.nodes.InsnNode;
import jadx.core.utils.InsnUtils;
import jadx.core.utils.Utils;
public class InvokeNode extends InsnNode {
public class InvokeNode extends InsnNode implements CallMthInterface {
private final InvokeType type;
private final MethodInfo mth;
@@ -9,7 +9,10 @@ public abstract class TargetInsnNode extends InsnNode {
super(type, argsCount);
}
public abstract void initBlocks(BlockNode curBlock);
public void initBlocks(BlockNode curBlock) {
}
public abstract boolean replaceTargetBlock(BlockNode origin, BlockNode replace);
public boolean replaceTargetBlock(BlockNode origin, BlockNode replace) {
return false;
}
}
@@ -35,7 +35,6 @@ public final class NamedArg extends InsnArg implements Named {
return false;
}
return name.equals(((NamedArg) o).name);
}
@Override
@@ -2,13 +2,14 @@ package jadx.core.dex.instructions.mods;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.CallMthInterface;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.InvokeNode;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
public class ConstructorInsn extends InsnNode {
public class ConstructorInsn extends InsnNode implements CallMthInterface {
private final MethodInfo callMth;
private final CallType callType;
@@ -13,9 +13,7 @@ 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;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -52,7 +50,7 @@ public class ClassNode extends LineAttrNode implements ILoadable, IDexNode {
private final List<MethodNode> methods;
private final List<FieldNode> fields;
private List<ClassNode> innerClasses = Collections.emptyList();
private List<ClassNode> innerClasses = new ArrayList<>();
// store decompiled code
private CodeWriter code;
@@ -133,14 +131,16 @@ public class ClassNode extends LineAttrNode implements ILoadable, IDexNode {
}
// empty synthetic class
public ClassNode(DexNode dex, ClassInfo clsInfo) {
public ClassNode(DexNode dex, String name, int accessFlags) {
this.dex = dex;
this.clsInfo = clsInfo;
this.interfaces = Collections.emptyList();
this.methods = Collections.emptyList();
this.fields = Collections.emptyList();
this.accessFlags = new AccessInfo(AccessFlags.ACC_PUBLIC | AccessFlags.ACC_SYNTHETIC, AFType.CLASS);
this.clsInfo = ClassInfo.fromName(dex.root(), name);
this.interfaces = new ArrayList<>();
this.methods = new ArrayList<>();
this.fields = new ArrayList<>();
this.accessFlags = new AccessInfo(accessFlags, AFType.CLASS);
this.parentClass = this;
dex.addClassNode(this);
}
private void loadAnnotations(ClassDef cls) {
@@ -321,6 +321,15 @@ public class ClassNode extends LineAttrNode implements ILoadable, IDexNode {
return null;
}
public FieldNode searchFieldByNameAndType(FieldInfo field) {
for (FieldNode f : fields) {
if (f.getFieldInfo().equalsNameAndType(field)) {
return f;
}
}
return null;
}
public FieldNode searchFieldByName(String name) {
for (FieldNode f : fields) {
if (f.getName().equals(name)) {
@@ -369,10 +378,8 @@ public class ClassNode extends LineAttrNode implements ILoadable, IDexNode {
}
public void addInnerClass(ClassNode cls) {
if (innerClasses.isEmpty()) {
innerClasses = new ArrayList<>(3);
}
innerClasses.add(cls);
cls.parentClass = this;
}
public boolean isEnum() {
@@ -46,12 +46,15 @@ public class DexNode implements IDexNode {
public void loadClasses() {
for (ClassDef cls : dexBuf.classDefs()) {
ClassNode clsNode = new ClassNode(this, cls);
classes.add(clsNode);
clsMap.put(clsNode.getClassInfo(), clsNode);
addClassNode(new ClassNode(this, cls));
}
}
public void addClassNode(ClassNode clsNode) {
classes.add(clsNode);
clsMap.put(clsNode.getClassInfo(), clsNode);
}
void initInnerClasses() {
// move inner classes
List<ClassNode> inner = new ArrayList<>();
@@ -147,16 +150,15 @@ public class DexNode implements IDexNode {
@Nullable
FieldNode deepResolveField(@NotNull ClassNode cls, FieldInfo fieldInfo) {
FieldNode field = cls.searchFieldByName(fieldInfo.getName());
FieldNode field = cls.searchFieldByNameAndType(fieldInfo);
if (field != null) {
return field;
}
FieldNode found;
ArgType superClass = cls.getSuperClass();
if (superClass != null) {
ClassNode superNode = resolveClass(superClass);
if (superNode != null) {
found = deepResolveField(superNode, fieldInfo);
FieldNode found = deepResolveField(superNode, fieldInfo);
if (found != null) {
return found;
}
@@ -165,7 +167,7 @@ public class DexNode implements IDexNode {
for (ArgType iFaceType : cls.getInterfaces()) {
ClassNode iFaceNode = resolveClass(iFaceType);
if (iFaceNode != null) {
found = deepResolveField(iFaceNode, fieldInfo);
FieldNode found = deepResolveField(iFaceNode, fieldInfo);
if (found != null) {
return found;
}
@@ -27,7 +27,6 @@ public class Edge {
}
Edge edge = (Edge) o;
return source.equals(edge.source) && target.equals(edge.target);
}
@Override
@@ -9,5 +9,4 @@ public interface IBranchRegion extends IRegion {
* NOTE: Contains 'null' elements for indicate empty branches.
*/
List<IContainer> getBranches();
}
@@ -276,6 +276,7 @@ public class InsnNode extends LineAttrNode {
}
return true;
}
/**
* 'Hard' equals, compare all arguments
*/
@@ -284,6 +285,7 @@ public class InsnNode extends LineAttrNode {
return true;
}
return isSame(other)
&& Objects.equals(result, other.result)
&& Objects.equals(arguments, other.arguments);
}
@@ -51,7 +51,7 @@ public class MethodNode extends LineAttrNode implements ILoadable, IDexNode {
private final MethodInfo mthInfo;
private final ClassNode parentClass;
private final AccessInfo accFlags;
private AccessInfo accFlags;
private final Method methodData;
private int regsCount;
@@ -96,16 +96,16 @@ public class MethodNode extends LineAttrNode implements ILoadable, IDexNode {
DexNode dex = parentClass.dex();
Code mthCode = dex.readCode(methodData);
regsCount = mthCode.getRegistersSize();
this.regsCount = mthCode.getRegistersSize();
initMethodTypes();
InsnDecoder decoder = new InsnDecoder(this);
decoder.decodeInsns(mthCode);
instructions = decoder.process();
codeSize = instructions.length;
this.instructions = decoder.process();
this.codeSize = instructions.length;
initTryCatches(mthCode);
initJumps();
initTryCatches(this, mthCode, instructions);
initJumps(instructions);
this.debugInfoOffset = mthCode.getDebugInfoOffset();
} catch (Exception e) {
@@ -257,37 +257,37 @@ public class MethodNode extends LineAttrNode implements ILoadable, IDexNode {
return genericMap;
}
private void initTryCatches(Code mthCode) {
InsnNode[] insnByOffset = instructions;
private static void initTryCatches(MethodNode mth, Code mthCode, InsnNode[] insnByOffset) {
CatchHandler[] catchBlocks = mthCode.getCatchHandlers();
Try[] tries = mthCode.getTries();
if (catchBlocks.length == 0 && tries.length == 0) {
return;
}
int hc = 0;
int handlersCount = 0;
Set<Integer> addrs = new HashSet<>();
List<TryCatchBlock> catches = new ArrayList<>(catchBlocks.length);
for (CatchHandler handler : catchBlocks) {
TryCatchBlock tcBlock = new TryCatchBlock();
catches.add(tcBlock);
for (int i = 0; i < handler.getAddresses().length; i++) {
int addr = handler.getAddresses()[i];
ClassInfo type = ClassInfo.fromDex(parentClass.dex(), handler.getTypeIndexes()[i]);
tcBlock.addHandler(this, addr, type);
int[] handlerAddrArr = handler.getAddresses();
for (int i = 0; i < handlerAddrArr.length; i++) {
int addr = handlerAddrArr[i];
ClassInfo type = ClassInfo.fromDex(mth.dex(), handler.getTypeIndexes()[i]);
tcBlock.addHandler(mth, addr, type);
addrs.add(addr);
hc++;
handlersCount++;
}
int addr = handler.getCatchAllAddress();
if (addr >= 0) {
tcBlock.addHandler(this, addr, null);
tcBlock.addHandler(mth, addr, null);
addrs.add(addr);
hc++;
handlersCount++;
}
}
if (hc > 0 && hc != addrs.size()) {
if (handlersCount > 0 && handlersCount != addrs.size()) {
// resolve nested try blocks:
// inner block contains all handlers from outer block => remove these handlers from inner block
// each handler must be only in one try/catch block
@@ -295,7 +295,7 @@ public class MethodNode extends LineAttrNode implements ILoadable, IDexNode {
for (TryCatchBlock ct2 : catches) {
if (ct1 != ct2 && ct2.containsAllHandlers(ct1)) {
for (ExceptionHandler h : ct1.getHandlers()) {
ct2.removeHandler(this, h);
ct2.removeHandler(mth, h);
h.setTryBlock(ct1);
}
}
@@ -309,6 +309,7 @@ public class MethodNode extends LineAttrNode implements ILoadable, IDexNode {
for (ExceptionHandler eh : ct.getHandlers()) {
int addr = eh.getHandleOffset();
ExcHandlerAttr ehAttr = new ExcHandlerAttr(ct, eh);
// TODO: don't override existing attribute
insnByOffset[addr].addAttr(ehAttr);
}
}
@@ -335,8 +336,7 @@ public class MethodNode extends LineAttrNode implements ILoadable, IDexNode {
}
}
private void initJumps() {
InsnNode[] insnByOffset = instructions;
private static void initJumps(InsnNode[] insnByOffset) {
for (int offset = 0; offset < insnByOffset.length; offset++) {
InsnNode insn = insnByOffset[offset];
if (insn == null) {
@@ -484,7 +484,18 @@ public class MethodNode extends LineAttrNode implements ILoadable, IDexNode {
exceptionHandlers = new ArrayList<>(2);
} else {
for (ExceptionHandler h : exceptionHandlers) {
if (h == handler || h.getHandleOffset() == handler.getHandleOffset()) {
if (h.equals(handler)) {
return h;
}
if (h.getHandleOffset() == handler.getHandleOffset()) {
if (h.getTryBlock() == handler.getTryBlock()) {
for (ClassInfo catchType : handler.getCatchTypes()) {
h.addCatchType(catchType);
}
} else {
// same handlers from different try blocks
// will merge later
}
return h;
}
}
@@ -588,6 +599,10 @@ public class MethodNode extends LineAttrNode implements ILoadable, IDexNode {
return accFlags;
}
public void setAccFlags(AccessInfo accFlags) {
this.accFlags = accFlags;
}
public Region getRegion() {
return region;
}
@@ -1,15 +0,0 @@
package jadx.core.dex.nodes;
import com.android.dx.rop.code.AccessFlags;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.instructions.args.ArgType;
public class ResRefField extends FieldNode {
public ResRefField(DexNode dex, String str) {
super(dex.root().getAppResClass(),
FieldInfo.from(dex, dex.root().getAppResClass().getClassInfo(), str, ArgType.INT),
AccessFlags.ACC_PUBLIC);
}
}
@@ -1,12 +1,8 @@
package jadx.core.dex.nodes;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import com.android.dex.Dex;
import jadx.core.dex.info.FieldInfo;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
@@ -19,16 +15,15 @@ import jadx.api.ResourcesLoader;
import jadx.core.clsp.ClspGraph;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.ConstStorage;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.info.InfoStorage;
import jadx.core.dex.info.MethodInfo;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.StringUtils;
import jadx.core.utils.android.AndroidResourcesUtils;
import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.exceptions.JadxRuntimeException;
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;
@@ -41,11 +36,12 @@ public class RootNode {
private final ConstStorage constValues;
private final InfoStorage infoStorage = new InfoStorage();
private ClspGraph clsp;
private List<DexNode> dexNodes;
@Nullable
private String appPackage;
@Nullable
private ClassNode appResClass;
private ClspGraph clsp;
public RootNode(JadxArgs args) {
this.args = args;
@@ -84,24 +80,22 @@ public class RootNode {
LOG.debug("'.arsc' file not found");
return;
}
ResTableParser parser = new ResTableParser();
try {
ResourcesLoader.decodeStream(arsc, (size, is) -> {
ResourceStorage resStorage = ResourcesLoader.decodeStream(arsc, (size, is) -> {
ResTableParser parser = new ResTableParser();
parser.decode(is);
return null;
return parser.getResStorage();
});
} catch (JadxException e) {
processResources(resStorage);
} catch (Exception e) {
LOG.error("Failed to parse '.arsc' file", e);
return;
}
ResourceStorage resStorage = parser.getResStorage();
constValues.setResourcesNames(resStorage.getResourcesNames());
appPackage = resStorage.getAppPackage();
}
public void initAppResClass() {
appResClass = AndroidResourcesUtils.searchAppResClass(this);
public void processResources(ResourceStorage resStorage) {
constValues.setResourcesNames(resStorage.getResourcesNames());
appPackage = resStorage.getAppPackage();
appResClass = AndroidResourcesUtils.searchAppResClass(this, resStorage);
}
public void initClassPath() {
@@ -24,7 +24,7 @@ public final class IfInfo {
}
private IfInfo(IfCondition condition, BlockNode thenBlock, BlockNode elseBlock,
Set<BlockNode> mergedBlocks, Set<BlockNode> skipBlocks) {
Set<BlockNode> mergedBlocks, Set<BlockNode> skipBlocks) {
this.condition = condition;
this.thenBlock = thenBlock;
this.elseBlock = elseBlock;
@@ -24,5 +24,4 @@ public class CatchAttr implements IAttribute {
public String toString() {
return tryBlock.toString();
}
}
@@ -30,6 +30,6 @@ public class ExcHandlerAttr implements IAttribute {
public String toString() {
return "ExcHandler: " + (handler.isFinally()
? " FINALLY"
: (handler.isCatchAll() ? "all" : handler.getCatchType()) + " " + handler.getArg());
: handler.catchTypeStr() + " " + handler.getArg());
}
}
@@ -1,18 +1,27 @@
package jadx.core.dex.trycatch;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.TreeSet;
import org.jetbrains.annotations.Nullable;
import jadx.core.Consts;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.IContainer;
import jadx.core.utils.InsnUtils;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxRuntimeException;
public class ExceptionHandler {
private final ClassInfo catchType;
private final Set<ClassInfo> catchTypes = new TreeSet<>();
private final int handleOffset;
private BlockNode handlerBlock;
@@ -23,17 +32,57 @@ public class ExceptionHandler {
private TryCatchBlock tryBlock;
private boolean isFinally;
public ExceptionHandler(int addr, ClassInfo type) {
public ExceptionHandler(int addr, @Nullable ClassInfo type) {
this.handleOffset = addr;
this.catchType = type;
addCatchType(type);
}
public ClassInfo getCatchType() {
return catchType;
/**
* Add exception type to catch block
* @param type - null for 'all' or 'Throwable' handler
*/
public void addCatchType(@Nullable ClassInfo type) {
if (type != null) {
this.catchTypes.add(type);
} else {
if (!this.catchTypes.isEmpty()) {
throw new JadxRuntimeException("Null type added to not empty exception handler: " + this);
}
}
}
public void addCatchTypes(Collection<ClassInfo> types) {
for (ClassInfo type : types) {
addCatchType(type);
}
}
public Set<ClassInfo> getCatchTypes() {
return catchTypes;
}
public ArgType getArgType() {
if (isCatchAll()) {
return ArgType.THROWABLE;
}
Set<ClassInfo> types = getCatchTypes();
if (types.size() == 1) {
return types.iterator().next().getType();
} else {
return ArgType.THROWABLE;
}
}
public boolean isCatchAll() {
return catchType == null || catchType.getFullName().equals(Consts.CLASS_THROWABLE);
if (catchTypes.isEmpty()) {
return true;
}
for (ClassInfo classInfo : catchTypes) {
if (classInfo.getFullName().equals(Consts.CLASS_THROWABLE)) {
return true;
}
}
return false;
}
public int getHandleOffset() {
@@ -89,36 +138,30 @@ public class ExceptionHandler {
}
@Override
public int hashCode() {
return (catchType == null ? 0 : 31 * catchType.hashCode()) + handleOffset;
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
ExceptionHandler that = (ExceptionHandler) o;
return handleOffset == that.handleOffset &&
catchTypes.equals(that.catchTypes) &&
Objects.equals(tryBlock, that.tryBlock);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
ExceptionHandler other = (ExceptionHandler) obj;
if (catchType == null) {
if (other.catchType != null) {
return false;
}
} else if (!catchType.equals(other.catchType)) {
return false;
}
return handleOffset == other.handleOffset;
public int hashCode() {
return Objects.hash(catchTypes, handleOffset /*, tryBlock*/);
}
public String catchTypeStr() {
return catchTypes.isEmpty() ? "all" : Utils.listToString(catchTypes, " | ", ClassInfo::getShortName);
}
@Override
public String toString() {
return (catchType == null ? "all"
: catchType.getShortName()) + " -> " + InsnUtils.formatOffset(handleOffset);
return catchTypeStr() + " -> " + InsnUtils.formatOffset(handleOffset);
}
}
@@ -23,7 +23,6 @@ public class SplitterBlockAttr implements IAttribute {
@Override
public String toString() {
return "Splitter: " + block;
return "Splitter:" + block;
}
}
@@ -5,6 +5,8 @@ import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import org.jetbrains.annotations.Nullable;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.info.ClassInfo;
@@ -40,12 +42,14 @@ public class TryCatchBlock {
return handlers.containsAll(tb.handlers);
}
public ExceptionHandler addHandler(MethodNode mth, int addr, ClassInfo type) {
public ExceptionHandler addHandler(MethodNode mth, int addr, @Nullable ClassInfo type) {
ExceptionHandler handler = new ExceptionHandler(addr, type);
handler = mth.addExceptionHandler(handler);
handlers.add(handler);
handler.setTryBlock(this);
return handler;
ExceptionHandler addedHandler = mth.addExceptionHandler(handler);
if (addedHandler == handler || addedHandler.getTryBlock() != this) {
handlers.add(addedHandler);
}
return addedHandler;
}
public void removeHandler(MethodNode mth, ExceptionHandler handler) {
@@ -1,6 +1,7 @@
package jadx.core.dex.visitors;
import java.util.List;
import java.util.Objects;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
@@ -41,7 +42,8 @@ public class ClassModifier extends AbstractVisitor {
}
if (cls.getAccessFlags().isSynthetic()
&& cls.getFields().isEmpty()
&& cls.getMethods().isEmpty()) {
&& cls.getMethods().isEmpty()
&& cls.getInnerClasses().isEmpty()) {
cls.add(AFlag.DONT_GENERATE);
return false;
}
@@ -216,6 +218,13 @@ public class ClassModifier extends AbstractVisitor {
MethodInfo callMth = ((InvokeNode) insn).getCallMth();
MethodNode wrappedMth = mth.root().deepResolveMethod(callMth);
if (wrappedMth != null) {
if (callMth.getArgsCount() != mth.getMethodInfo().getArgsCount()) {
return false;
}
// rename method only from current class
if (!mth.getParentClass().equals(wrappedMth.getParentClass())) {
return false;
}
// all args must be registers passed from method args (allow only casts insns)
for (InsnArg arg : insn.getArguments()) {
if (!registersAndCastsOnly(arg)) {
@@ -223,9 +232,13 @@ public class ClassModifier extends AbstractVisitor {
}
}
String alias = mth.getAlias();
if (!wrappedMth.getAlias().equals(alias) && wrappedMth.isVirtual()) {
wrappedMth.getMethodInfo().setAlias(alias);
if (Objects.equals(wrappedMth.getAlias(), alias)) {
return true;
}
if (!wrappedMth.isVirtual()) {
return false;
}
wrappedMth.getMethodInfo().setAlias(alias);
return true;
}
}
@@ -260,7 +260,7 @@ public class CodeShrinker extends AbstractVisitor {
}
private static boolean canMoveBetweenBlocks(InsnNode assignInsn, BlockNode assignBlock,
BlockNode useBlock, InsnNode useInsn) {
BlockNode useBlock, InsnNode useInsn) {
if (!BlockUtils.isPathExists(assignBlock, useBlock)) {
return false;
}
@@ -3,6 +3,8 @@ package jadx.core.dex.visitors;
import java.util.ArrayList;
import java.util.List;
import org.jetbrains.annotations.Nullable;
import jadx.core.codegen.TypeGen;
import jadx.core.deobf.NameMapper;
import jadx.core.dex.attributes.AFlag;
@@ -27,7 +29,6 @@ import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.InsnUtils;
import jadx.core.utils.exceptions.JadxException;
import org.jetbrains.annotations.Nullable;
@JadxVisitor(
name = "EnumVisitor",
@@ -51,7 +52,7 @@ public class EnumVisitor extends AbstractVisitor {
}
}
if (staticMethod == null) {
ErrorsCounter.classError(cls, "Enum class init method not found");
ErrorsCounter.classWarn(cls, "Enum class init method not found");
return true;
}
@@ -0,0 +1,57 @@
package jadx.core.dex.visitors;
import com.android.dx.rop.code.AccessFlags;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
@JadxVisitor(
name = "FixAccessModifiers",
desc = "Change class and method access modifiers if needed",
runAfter = ModVisitor.class
)
public class FixAccessModifiers extends AbstractVisitor {
private boolean respectAccessModifiers;
@Override
public void init(RootNode root) {
this.respectAccessModifiers = root.getArgs().isRespectBytecodeAccModifiers();
}
@Override
public void visit(MethodNode mth) {
if (respectAccessModifiers) {
return;
}
AccessInfo accessFlags = mth.getAccessFlags();
int newVisFlag = fixVisibility(mth, accessFlags);
if (newVisFlag != 0) {
AccessInfo newAccFlags = accessFlags.changeVisibility(newVisFlag);
if (newAccFlags != accessFlags) {
mth.setAccFlags(newAccFlags);
mth.addAttr(AType.COMMENTS, "Access modifiers changed, original: " + accessFlags.rawString());
}
}
}
private int fixVisibility(MethodNode mth, AccessInfo accessFlags) {
if (mth.isVirtual()) {
// make virtual methods public
return AccessFlags.ACC_PUBLIC;
} else {
if (accessFlags.isAbstract()) {
// make abstract methods public
return AccessFlags.ACC_PUBLIC;
}
if (accessFlags.isConstructor() || accessFlags.isStatic()) {
// TODO: make public if used outside
return 0;
}
// make other direct methods private
return AccessFlags.ACC_PRIVATE;
}
}
}
@@ -438,7 +438,7 @@ public class ModVisitor extends AbstractVisitor {
// result arg used both in this insn and exception handler,
RegisterArg resArg = insn.getResult();
ArgType type = excHandler.isCatchAll() ? ArgType.THROWABLE : excHandler.getCatchType().getType();
ArgType type = excHandler.getArgType();
String name = excHandler.isCatchAll() ? "th" : "e";
if (resArg.getName() == null) {
resArg.setName(name);
@@ -10,7 +10,9 @@ import org.apache.commons.io.FilenameUtils;
import jadx.api.JadxArgs;
import jadx.core.Consts;
import jadx.core.deobf.Deobfuscator;
import jadx.core.deobf.NameMapper;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.nodes.ClassNode;
@@ -18,7 +20,6 @@ import jadx.core.dex.nodes.DexNode;
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.FileUtils;
import jadx.core.utils.files.InputFile;
@@ -48,20 +49,12 @@ public class RenameVisitor extends AbstractVisitor {
checkClasses(root, isCaseSensitive);
}
@Override
public boolean visit(ClassNode cls) throws JadxException {
checkFields(cls);
checkMethods(cls);
for (ClassNode inner : cls.getInnerClasses()) {
visit(inner);
}
return false;
}
private void checkClasses(RootNode root, boolean caseSensitive) {
Set<String> clsNames = new HashSet<>();
for (ClassNode cls : root.getClasses(true)) {
checkClassName(cls);
checkFields(cls);
checkMethods(cls);
if (!caseSensitive) {
ClassInfo classInfo = cls.getClassInfo();
String clsFileName = classInfo.getAlias().getFullPath();
@@ -100,8 +93,9 @@ public class RenameVisitor extends AbstractVisitor {
Set<String> names = new HashSet<>();
for (FieldNode field : cls.getFields()) {
FieldInfo fieldInfo = field.getFieldInfo();
if (!names.add(fieldInfo.getAlias())) {
deobfuscator.renameField(field);
String fieldName = fieldInfo.getAlias();
if (!names.add(fieldName) || !NameMapper.isValidIdentifier(fieldName)) {
deobfuscator.forceRenameField(field);
}
}
}
@@ -109,12 +103,16 @@ public class RenameVisitor extends AbstractVisitor {
private void checkMethods(ClassNode cls) {
Set<String> names = new HashSet<>();
for (MethodNode mth : cls.getMethods()) {
if (mth.contains(AFlag.DONT_GENERATE)) {
AccessInfo accessFlags = mth.getAccessFlags();
if (accessFlags.isConstructor()
|| accessFlags.isBridge()
|| accessFlags.isSynthetic()
|| mth.contains(AFlag.DONT_GENERATE)) {
continue;
}
String signature = mth.getMethodInfo().makeSignature(false);
if (!names.add(signature)) {
deobfuscator.renameMethod(mth);
if (!names.add(signature) || !NameMapper.isValidIdentifier(mth.getAlias())) {
deobfuscator.forceRenameMethod(mth);
}
}
}
@@ -4,19 +4,13 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import jadx.core.dex.instructions.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.core.Consts;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.ArithNode;
import jadx.core.dex.instructions.ArithOp;
import jadx.core.dex.instructions.ConstStringNode;
import jadx.core.dex.instructions.IfNode;
import jadx.core.dex.instructions.IndexInsnNode;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.InvokeNode;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.FieldArg;
import jadx.core.dex.instructions.args.InsnArg;
@@ -148,6 +142,15 @@ public class SimplifyVisitor extends AbstractVisitor {
}
}
/**
* Simplify chains of calls to StringBuilder#append() plus constructor of StringBuilder.
* Those chains are usually automatically generated by the Java compiler when you create String
* concatenations like <code>"text " + 1 + " text"</code>.
*
* @param mth
* @param insn
* @return
*/
private static InsnNode convertInvoke(MethodNode mth, InsnNode insn) {
MethodInfo callMth = ((InvokeNode) insn).getCallMth();
@@ -201,13 +204,20 @@ public class SimplifyVisitor extends AbstractVisitor {
}
for (; argInd < len; argInd++) { // Add the .append(xxx) arg string to concat
concatInsn.addArg(chain.get(argInd).getArg(1));
InsnNode node = chain.get(argInd);
MethodInfo method = ((CallMthInterface) node).getCallMth();
if (!(node.getArgsCount() < 2 && method.isConstructor() || method.getName().equals("append"))) {
// The chain contains other calls to StringBuilder methods than the constructor or append.
// We can't simplify such chains, therefore we leave them as they are.
return null;
}
// process only constructor and append() calls
concatInsn.addArg(node.getArg(1));
}
concatInsn.setResult(insn.getResult());
return concatInsn;
} // end of if constructor is for StringBuilder
} // end of if we found a constructor early in the chain
} catch (Exception e) {
LOG.warn("Can't convert string concatenation: {} insn: {}", mth, insn, e);
}
@@ -1,8 +1,5 @@
package jadx.core.dex.visitors.blocksmaker;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.instructions.InsnType;
@@ -23,8 +20,6 @@ import jadx.core.utils.InstructionRemover;
public class BlockExceptionHandler extends AbstractVisitor {
private static final Logger LOG = LoggerFactory.getLogger(BlockExceptionHandler.class);
@Override
public void visit(MethodNode mth) {
if (mth.isNoCode()) {
@@ -40,7 +35,7 @@ public class BlockExceptionHandler extends AbstractVisitor {
processExceptionHandlers(mth, block);
}
for (BlockNode block : mth.getBasicBlocks()) {
processTryCatchBlocks(mth, block);
processTryCatchBlocks(block);
}
}
@@ -48,27 +43,26 @@ public class BlockExceptionHandler extends AbstractVisitor {
* Set exception handler attribute for whole block
*/
private static void markExceptionHandlers(BlockNode block) {
if (block.getInstructions().isEmpty()) {
return;
}
InsnNode me = block.getInstructions().get(0);
ExcHandlerAttr handlerAttr = me.get(AType.EXC_HANDLER);
ExcHandlerAttr handlerAttr = block.get(AType.EXC_HANDLER);
if (handlerAttr == null) {
return;
}
ExceptionHandler excHandler = handlerAttr.getHandler();
block.addAttr(handlerAttr);
ArgType argType = excHandler.isCatchAll() ? ArgType.THROWABLE : excHandler.getCatchType().getType();
if (me.getType() == InsnType.MOVE_EXCEPTION) {
// set correct type for 'move-exception' operation
RegisterArg resArg = InsnArg.reg(me.getResult().getRegNum(), argType);
me.setResult(resArg);
me.add(AFlag.DONT_INLINE);
excHandler.setArg(resArg);
} else {
// handler arguments not used
excHandler.setArg(new NamedArg("unused", argType));
ArgType argType = excHandler.getArgType();
if (!block.getInstructions().isEmpty()) {
InsnNode me = block.getInstructions().get(0);
if (me.getType() == InsnType.MOVE_EXCEPTION) {
// set correct type for 'move-exception' operation
RegisterArg resArg = InsnArg.reg(me.getResult().getRegNum(), argType);
resArg.copyAttributesFrom(me);
me.setResult(resArg);
me.add(AFlag.DONT_INLINE);
excHandler.setArg(resArg);
return;
}
}
// handler arguments not used
excHandler.setArg(new NamedArg("unused", argType));
}
private static void processExceptionHandlers(MethodNode mth, BlockNode block) {
@@ -113,9 +107,7 @@ public class BlockExceptionHandler extends AbstractVisitor {
private static boolean onlyAllHandler(TryCatchBlock tryBlock) {
if (tryBlock.getHandlersCount() == 1) {
ExceptionHandler eh = tryBlock.getHandlers().iterator().next();
if (eh.isCatchAll() || eh.isFinally()) {
return true;
}
return eh.isCatchAll() || eh.isFinally();
}
return false;
}
@@ -123,7 +115,7 @@ public class BlockExceptionHandler extends AbstractVisitor {
/**
* If all instructions in block have same 'catch' attribute mark it as 'TryCatch' block.
*/
private static void processTryCatchBlocks(MethodNode mth, BlockNode block) {
private static void processTryCatchBlocks(BlockNode block) {
CatchAttr commonCatchAttr = null;
for (InsnNode insn : block.getInstructions()) {
CatchAttr catchAttr = insn.get(AType.CATCH_BLOCK);
@@ -139,24 +131,6 @@ public class BlockExceptionHandler extends AbstractVisitor {
}
if (commonCatchAttr != null) {
block.addAttr(commonCatchAttr);
// connect handler to block
for (ExceptionHandler handler : commonCatchAttr.getTryBlock().getHandlers()) {
connectHandler(mth, handler);
}
}
}
private static void connectHandler(MethodNode mth, ExceptionHandler handler) {
int addr = handler.getHandleOffset();
for (BlockNode block : mth.getBasicBlocks()) {
ExcHandlerAttr bh = block.get(AType.EXC_HANDLER);
if (bh != null && bh.getHandler().getHandleOffset() == addr) {
handler.setHandlerBlock(block);
break;
}
}
if (handler.getHandlerBlock() == null) {
LOG.warn("Exception handler block not set for {}, mth: {}", handler, mth);
}
}
}
@@ -26,7 +26,7 @@ public class BlockFinish extends AbstractVisitor {
for (BlockNode block : mth.getBasicBlocks()) {
block.updateCleanSuccessors();
fixSplitterBlock(block);
fixSplitterBlock(mth, block);
}
mth.finishBasicBlocks();
@@ -36,7 +36,7 @@ public class BlockFinish extends AbstractVisitor {
* For evey exception handler must be only one splitter block,
* select correct one and remove others if necessary.
*/
private static void fixSplitterBlock(BlockNode block) {
private static void fixSplitterBlock(MethodNode mth, BlockNode block) {
ExcHandlerAttr excHandlerAttr = block.get(AType.EXC_HANDLER);
if (excHandlerAttr == null) {
return;
@@ -58,7 +58,7 @@ public class BlockFinish extends AbstractVisitor {
}
BlockNode topSplitter = BlockUtils.getTopBlock(splitters.keySet());
if (topSplitter == null) {
LOG.warn("Unknown top splitter block from list: {}", splitters);
mth.addWarn("Unknown top exception splitter block from list: " + splitters);
return;
}
for (Map.Entry<BlockNode, SplitterBlockAttr> entry : splitters.entrySet()) {
@@ -2,6 +2,7 @@ package jadx.core.dex.visitors.blocksmaker;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
@@ -19,6 +20,9 @@ import jadx.core.dex.nodes.Edge;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.trycatch.CatchAttr;
import jadx.core.dex.trycatch.ExcHandlerAttr;
import jadx.core.dex.trycatch.ExceptionHandler;
import jadx.core.dex.trycatch.TryCatchBlock;
import jadx.core.dex.visitors.AbstractVisitor;
import jadx.core.utils.BlockUtils;
import jadx.core.utils.exceptions.JadxOverflowException;
@@ -360,7 +364,10 @@ public class BlockProcessor extends AbstractVisitor {
throw new JadxRuntimeException("Unreachable block: " + block);
}
}
if (mergeExceptionHandlers(mth)) {
removeMarkedBlocks(mth);
return true;
}
for (BlockNode block : basicBlocks) {
if (checkLoops(mth, block)) {
return true;
@@ -445,6 +452,85 @@ public class BlockProcessor extends AbstractVisitor {
return false;
}
/**
* Merge handlers for multi-exception catch
*/
private static boolean mergeExceptionHandlers(MethodNode mth) {
for (BlockNode block : mth.getBasicBlocks()) {
ExcHandlerAttr excHandlerAttr = block.get(AType.EXC_HANDLER);
if (excHandlerAttr != null) {
List<BlockNode> blocksForMerge = collectExcHandlerBlocks(block, excHandlerAttr);
if (mergeHandlers(mth, blocksForMerge, excHandlerAttr)) {
return true;
}
}
}
return false;
}
private static List<BlockNode> collectExcHandlerBlocks(BlockNode block, ExcHandlerAttr excHandlerAttr) {
List<BlockNode> successors = block.getSuccessors();
if (successors.size() != 1) {
return Collections.emptyList();
}
RegisterArg reg = getMoveExceptionRegister(block);
if (reg == null) {
return Collections.emptyList();
}
TryCatchBlock tryBlock = excHandlerAttr.getTryBlock();
List<BlockNode> blocksForMerge = new ArrayList<>();
BlockNode nextBlock = successors.get(0);
for (BlockNode predBlock : nextBlock.getPredecessors()) {
if (predBlock != block
&& checkOtherExcHandler(predBlock, tryBlock, reg)) {
blocksForMerge.add(predBlock);
}
}
return blocksForMerge;
}
private static boolean checkOtherExcHandler(BlockNode predBlock, TryCatchBlock tryBlock, RegisterArg reg) {
ExcHandlerAttr otherExcHandlerAttr = predBlock.get(AType.EXC_HANDLER);
if (otherExcHandlerAttr == null) {
return false;
}
TryCatchBlock otherTryBlock = otherExcHandlerAttr.getTryBlock();
if (tryBlock != otherTryBlock) {
return false;
}
RegisterArg otherReg = getMoveExceptionRegister(predBlock);
if (otherReg == null || reg.getRegNum() != otherReg.getRegNum()) {
return false;
}
return true;
}
private static RegisterArg getMoveExceptionRegister(BlockNode block) {
if (block.getInstructions().isEmpty()) {
return null;
}
InsnNode insn = block.getInstructions().get(0);
if (insn.getType() != InsnType.MOVE_EXCEPTION) {
return null;
}
return insn.getResult();
}
private static boolean mergeHandlers(MethodNode mth, List<BlockNode> blocksForMerge, ExcHandlerAttr excHandlerAttr) {
if (blocksForMerge.isEmpty()) {
return false;
}
TryCatchBlock tryBlock = excHandlerAttr.getTryBlock();
for (BlockNode block : blocksForMerge) {
ExcHandlerAttr otherExcHandlerAttr = block.get(AType.EXC_HANDLER);
ExceptionHandler excHandler = otherExcHandlerAttr.getHandler();
excHandlerAttr.getHandler().addCatchTypes(excHandler.getCatchTypes());
tryBlock.removeHandler(mth, excHandler);
detachBlock(block);
}
return true;
}
/**
* Splice return block if several predecessors presents
*/
@@ -543,6 +629,20 @@ public class BlockProcessor extends AbstractVisitor {
});
}
private static void detachBlock(BlockNode block) {
for (BlockNode pred : block.getPredecessors()) {
pred.getSuccessors().remove(block);
pred.updateCleanSuccessors();
}
for (BlockNode successor : block.getSuccessors()) {
successor.getPredecessors().remove(block);
}
block.add(AFlag.REMOVE);
block.getInstructions().clear();
block.getPredecessors().clear();
block.getSuccessors().clear();
}
private static void clearBlocksState(MethodNode mth) {
mth.getBasicBlocks().forEach(block -> {
block.remove(AType.LOOP);
@@ -16,6 +16,7 @@ import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.trycatch.CatchAttr;
import jadx.core.dex.trycatch.ExcHandlerAttr;
import jadx.core.dex.trycatch.ExceptionHandler;
import jadx.core.dex.trycatch.SplitterBlockAttr;
import jadx.core.dex.visitors.AbstractVisitor;
@@ -43,6 +44,7 @@ public class BlockSplitter extends AbstractVisitor {
mth.initBasicBlocks();
splitBasicBlocks(mth);
removeJumpAttr(mth);
removeInsns(mth);
removeEmptyDetachedBlocks(mth);
initBlocksInTargetNodes(mth);
@@ -81,11 +83,11 @@ public class BlockSplitter extends AbstractVisitor {
if (type == InsnType.RETURN || type == InsnType.THROW) {
mth.addExitBlock(curBlock);
}
BlockNode block = startNewBlock(mth, insn.getOffset());
BlockNode newBlock = startNewBlock(mth, insn.getOffset());
if (type == InsnType.MONITOR_ENTER || type == InsnType.MONITOR_EXIT) {
connect(curBlock, block);
connect(curBlock, newBlock);
}
curBlock = block;
curBlock = newBlock;
startNew = true;
} else {
startNew = isSplitByJump(prevInsn, insn)
@@ -95,40 +97,79 @@ public class BlockSplitter extends AbstractVisitor {
|| prevInsn.contains(AFlag.TRY_LEAVE)
|| prevInsn.getType() == InsnType.MOVE_EXCEPTION;
if (startNew) {
BlockNode block = startNewBlock(mth, insn.getOffset());
connect(curBlock, block);
curBlock = block;
curBlock = connectNewBlock(mth, curBlock, insn.getOffset());
}
}
}
// for try/catch make empty block for connect handlers
if (insn.contains(AFlag.TRY_ENTER)) {
BlockNode block;
if (insn.getOffset() != 0 && !startNew) {
block = startNewBlock(mth, insn.getOffset());
connect(curBlock, block);
curBlock = block;
}
curBlock = insertSplitterBlock(mth, blocksMap, curBlock, insn, startNew);
} else if (insn.contains(AType.EXC_HANDLER)) {
processExceptionHandler(mth, curBlock, insn);
blocksMap.put(insn.getOffset(), curBlock);
// add this insn in new block
block = startNewBlock(mth, -1);
curBlock.add(AFlag.SYNTHETIC);
SplitterBlockAttr splitter = new SplitterBlockAttr(curBlock);
block.addAttr(splitter);
curBlock.addAttr(splitter);
connect(curBlock, block);
curBlock = block;
curBlock.getInstructions().add(insn);
} else {
blocksMap.put(insn.getOffset(), curBlock);
curBlock.getInstructions().add(insn);
}
curBlock.getInstructions().add(insn);
prevInsn = insn;
}
// setup missing connections
setupConnections(mth, blocksMap);
}
/**
* Make separate block for exception handler. New block already added if MOVE_EXCEPTION insn exists.
* Also link ExceptionHandler with current block.
*/
private static void processExceptionHandler(MethodNode mth, BlockNode curBlock, InsnNode insn) {
ExcHandlerAttr excHandlerAttr = insn.get(AType.EXC_HANDLER);
insn.remove(AType.EXC_HANDLER);
BlockNode excHandlerBlock;
if (insn.getType() == InsnType.MOVE_EXCEPTION) {
excHandlerBlock = curBlock;
} else {
BlockNode newBlock = startNewBlock(mth, -1);
newBlock.add(AFlag.SYNTHETIC);
connect(newBlock, curBlock);
excHandlerBlock = newBlock;
}
excHandlerBlock.addAttr(excHandlerAttr);
excHandlerAttr.getHandler().setHandlerBlock(excHandlerBlock);
}
/**
* For try/catch make empty (splitter) block for connect handlers
*/
private static BlockNode insertSplitterBlock(MethodNode mth, Map<Integer, BlockNode> blocksMap,
BlockNode curBlock, InsnNode insn, boolean startNew) {
BlockNode splitterBlock;
if (insn.getOffset() == 0 || startNew) {
splitterBlock = curBlock;
} else {
splitterBlock = connectNewBlock(mth, curBlock, insn.getOffset());
}
blocksMap.put(insn.getOffset(), splitterBlock);
SplitterBlockAttr splitterAttr = new SplitterBlockAttr(splitterBlock);
splitterBlock.add(AFlag.SYNTHETIC);
splitterBlock.addAttr(splitterAttr);
// add this insn in new block
BlockNode newBlock = startNewBlock(mth, -1);
newBlock.getInstructions().add(insn);
newBlock.addAttr(splitterAttr);
connect(splitterBlock, newBlock);
return newBlock;
}
private static BlockNode connectNewBlock(MethodNode mth, BlockNode curBlock, int offset) {
BlockNode block = startNewBlock(mth, offset);
connect(curBlock, block);
return block;
}
static BlockNode startNewBlock(MethodNode mth, int offset) {
BlockNode block = new BlockNode(mth.getBasicBlocks().size(), offset);
mth.getBasicBlocks().add(block);
@@ -183,12 +224,13 @@ public class BlockSplitter extends AbstractVisitor {
BlockNode thisBlock = getBlock(jump.getDest(), blocksMap);
connect(srcBlock, thisBlock);
}
connectExceptionHandlers(blocksMap, block, insn);
connectExceptionHandlers(block, insn, blocksMap);
}
}
}
private static void connectExceptionHandlers(Map<Integer, BlockNode> blocksMap, BlockNode block, InsnNode insn) {
private static void connectExceptionHandlers(BlockNode block, InsnNode insn,
Map<Integer, BlockNode> blocksMap) {
CatchAttr catches = insn.get(AType.CATCH_BLOCK);
SplitterBlockAttr spl = block.get(AType.SPLITTER_BLOCK);
if (catches == null || spl == null) {
@@ -197,7 +239,7 @@ public class BlockSplitter extends AbstractVisitor {
BlockNode splitterBlock = spl.getBlock();
boolean tryEnd = insn.contains(AFlag.TRY_LEAVE);
for (ExceptionHandler h : catches.getTryBlock().getHandlers()) {
BlockNode handlerBlock = getBlock(h.getHandleOffset(), blocksMap);
BlockNode handlerBlock = initHandlerBlock(h, blocksMap);
// skip self loop in handler
if (splitterBlock != handlerBlock) {
if (!handlerBlock.contains(AType.SPLITTER_BLOCK)) {
@@ -211,6 +253,16 @@ public class BlockSplitter extends AbstractVisitor {
}
}
private static BlockNode initHandlerBlock(ExceptionHandler excHandler, Map<Integer, BlockNode> blocksMap) {
BlockNode handlerBlock = excHandler.getHandlerBlock();
if (handlerBlock != null) {
return handlerBlock;
}
BlockNode blockByOffset = getBlock(excHandler.getHandleOffset(), blocksMap);
excHandler.setHandlerBlock(blockByOffset);
return blockByOffset;
}
private static boolean isSplitByJump(InsnNode prevInsn, InsnNode currentInsn) {
List<JumpInfo> pJumps = prevInsn.getAll(AType.JUMP);
for (JumpInfo jump : pJumps) {
@@ -245,9 +297,20 @@ public class BlockSplitter extends AbstractVisitor {
return block;
}
private void removeJumpAttr(MethodNode mth) {
for (BlockNode block : mth.getBasicBlocks()) {
for (InsnNode insn : block.getInstructions()) {
insn.remove(AType.JUMP);
}
}
}
private static void removeInsns(MethodNode mth) {
for (BlockNode block : mth.getBasicBlocks()) {
block.getInstructions().removeIf(insn -> {
if (!insn.isAttrStorageEmpty()) {
return false;
}
InsnType insnType = insn.getType();
return insnType == InsnType.GOTO || insnType == InsnType.NOP;
});
@@ -18,5 +18,4 @@ public abstract class AbstractRegionVisitor implements IRegionVisitor {
@Override
public void leaveRegion(MethodNode mth, IRegion region) {
}
}
@@ -45,14 +45,12 @@ public class CheckRegions extends AbstractVisitor {
if (blocksInRegions.add(block)) {
return;
}
if (!block.contains(AFlag.RETURN)
if (LOG.isDebugEnabled()
&& !block.contains(AFlag.RETURN)
&& !block.contains(AFlag.SKIP)
&& !block.contains(AFlag.SYNTHETIC)
&& !block.getInstructions().isEmpty()) {
if (LOG.isDebugEnabled()) {
LOG.debug("Duplicated block: {} - {}", mth, block);
}
//mth.addWarn("Duplicated block: " + block);
LOG.debug("Duplicated block: {} - {}", mth, block);
}
}
});
@@ -62,7 +60,7 @@ public class CheckRegions extends AbstractVisitor {
&& !block.getInstructions().isEmpty()
&& !block.contains(AFlag.SKIP)) {
String blockCode = getBlockInsnStr(mth, block);
mth.addWarn("Missing block: " + block + ", code:" + CodeWriter.NL + blockCode);
mth.addWarn("Missing block: " + block + ", code skipped:" + CodeWriter.NL + blockCode);
}
}
}
@@ -90,7 +88,8 @@ public class CheckRegions extends AbstractVisitor {
for (InsnNode insn : block.getInstructions()) {
try {
ig.makeInsn(insn, code);
} catch (CodegenException ignored) {
} catch (CodegenException e) {
// ignore
}
}
code.newLine().addIndent();
@@ -61,7 +61,7 @@ public class DepthRegionTraversal {
}
private static boolean traverseIterativeStepInternal(MethodNode mth, IRegionIterativeVisitor visitor,
IContainer container) {
IContainer container) {
if (container instanceof IRegion) {
IRegion region = (IRegion) container;
if (visitor.visitRegion(mth, region)) {
@@ -9,5 +9,4 @@ public interface IRegionIterativeVisitor {
* If return 'true' traversal will be restarted.
*/
boolean visitRegion(MethodNode mth, IRegion region);
}
@@ -14,5 +14,4 @@ public interface IRegionVisitor {
boolean enterRegion(MethodNode mth, IRegion region);
void leaveRegion(MethodNode mth, IRegion region);
}
@@ -126,7 +126,7 @@ public class LoopRegionVisitor extends AbstractVisitor implements IRegionVisitor
}
private static LoopType checkArrayForEach(MethodNode mth, InsnNode initInsn, InsnNode incrInsn,
IfCondition condition) {
IfCondition condition) {
if (!(incrInsn instanceof ArithNode)) {
return null;
}
@@ -27,7 +27,6 @@ import jadx.core.dex.trycatch.TryCatchBlock;
import jadx.core.utils.BlockUtils;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.RegionUtils;
import jadx.core.utils.exceptions.JadxRuntimeException;
/**
* Extract blocks to separate try/catch region
@@ -67,34 +66,39 @@ public class ProcessTryCatchRegions extends AbstractRegionVisitor {
// for each try block search nearest dominator block
for (TryCatchBlock tb : tryBlocks) {
if (tb.getHandlersCount() == 0) {
LOG.warn("No exception handlers in catch block, method: {}", mth);
mth.addWarn("No exception handlers in catch block: " + tb);
continue;
}
BitSet bs = new BitSet(mth.getBasicBlocks().size());
for (ExceptionHandler excHandler : tb.getHandlers()) {
BlockNode handlerBlock = excHandler.getHandlerBlock();
if (handlerBlock != null) {
SplitterBlockAttr splitter = handlerBlock.get(AType.SPLITTER_BLOCK);
if (splitter != null) {
BlockNode block = splitter.getBlock();
bs.set(block.getId());
}
processTryCatchBlock(mth, tb, tryBlocksMap);
}
}
private static void processTryCatchBlock(MethodNode mth, TryCatchBlock tb, Map<BlockNode, TryCatchBlock> tryBlocksMap) {
BitSet bs = new BitSet(mth.getBasicBlocks().size());
for (ExceptionHandler excHandler : tb.getHandlers()) {
BlockNode handlerBlock = excHandler.getHandlerBlock();
if (handlerBlock != null) {
SplitterBlockAttr splitter = handlerBlock.get(AType.SPLITTER_BLOCK);
if (splitter != null) {
BlockNode block = splitter.getBlock();
bs.set(block.getId());
}
}
List<BlockNode> domBlocks = BlockUtils.bitSetToBlocks(mth, bs);
BlockNode domBlock;
if (domBlocks.size() != 1) {
domBlock = BlockUtils.getTopBlock(domBlocks);
if (domBlock == null) {
throw new JadxRuntimeException("Exception block dominator not found, method:" + mth + ", dom blocks: " + domBlocks);
}
} else {
domBlock = domBlocks.get(0);
}
TryCatchBlock prevTB = tryBlocksMap.put(domBlock, tb);
if (prevTB != null) {
ErrorsCounter.methodWarn(mth, "Failed to process nested try/catch");
}
List<BlockNode> domBlocks = BlockUtils.bitSetToBlocks(mth, bs);
BlockNode domBlock;
if (domBlocks.size() != 1) {
domBlock = BlockUtils.getTopBlock(domBlocks);
if (domBlock == null) {
mth.addWarn("Exception block dominator not found, dom blocks: " + domBlocks);
return;
}
} else {
domBlock = domBlocks.get(0);
}
TryCatchBlock prevTB = tryBlocksMap.put(domBlock, tb);
if (prevTB != null) {
mth.addWarn("Failed to process nested try/catch");
}
}
@@ -105,7 +109,7 @@ public class ProcessTryCatchRegions extends AbstractRegionVisitor {
if (region.getSubBlocks().contains(dominator)) {
TryCatchBlock tb = tryBlocksMap.get(dominator);
if (!wrapBlocks(region, tb, dominator)) {
ErrorsCounter.methodWarn(mth, "Can't wrap try/catch for " + region);
ErrorsCounter.methodWarn(mth, "Can't wrap try/catch for region: " + region);
}
tryBlocksMap.remove(dominator);
return true;
@@ -55,16 +55,16 @@ import static jadx.core.utils.BlockUtils.skipSyntheticSuccessor;
public class RegionMaker {
private static final Logger LOG = LoggerFactory.getLogger(RegionMaker.class);
// 'dumb' guard to prevent endless loop in regions processing
private static final int REGIONS_LIMIT = 1000 * 1000;
private final MethodNode mth;
private final int regionsLimit;
private int regionsCount;
private BitSet processedBlocks;
public RegionMaker(MethodNode mth) {
this.mth = mth;
this.processedBlocks = new BitSet(mth.getBasicBlocks().size());
int blocksCount = mth.getBasicBlocks().size();
this.processedBlocks = new BitSet(blocksCount);
this.regionsLimit = blocksCount * 100;
}
public Region makeRegion(BlockNode startBlock, RegionStack stack) {
@@ -84,7 +84,7 @@ public class RegionMaker {
while (next != null) {
next = traverse(r, next, stack);
regionsCount++;
if (regionsCount > REGIONS_LIMIT) {
if (regionsCount > regionsLimit) {
throw new JadxRuntimeException("Regions count limit reached");
}
}
@@ -929,7 +929,7 @@ public class RegionMaker {
}
}
for (ExceptionHandler handler : tc.getHandlers()) {
processExcHandler(handler, exits);
processExcHandler(mth, handler, exits);
}
}
return processHandlersOutBlocks(mth, tcs);
@@ -968,12 +968,12 @@ public class RegionMaker {
return excOutRegion;
}
private void processExcHandler(ExceptionHandler handler, Set<BlockNode> exits) {
private void processExcHandler(MethodNode mth, ExceptionHandler handler, Set<BlockNode> exits) {
BlockNode start = handler.getHandlerBlock();
if (start == null) {
return;
}
RegionStack stack = new RegionStack(mth);
RegionStack stack = new RegionStack(this.mth);
BlockNode dom;
if (handler.isFinally()) {
SplitterBlockAttr splitterAttr = start.get(AType.SPLITTER_BLOCK);
@@ -986,11 +986,11 @@ public class RegionMaker {
stack.addExits(exits);
}
BitSet domFrontier = dom.getDomFrontier();
List<BlockNode> handlerExits = BlockUtils.bitSetToBlocks(mth, domFrontier);
boolean inLoop = mth.getLoopForBlock(start) != null;
List<BlockNode> handlerExits = BlockUtils.bitSetToBlocks(this.mth, domFrontier);
boolean inLoop = this.mth.getLoopForBlock(start) != null;
for (BlockNode exit : handlerExits) {
if ((!inLoop || BlockUtils.isPathExists(start, exit))
&& RegionUtils.isRegionContainsBlock(mth.getRegion(), exit)) {
&& RegionUtils.isRegionContainsBlock(this.mth.getRegion(), exit)) {
stack.addExit(exit);
}
}
@@ -998,7 +998,7 @@ public class RegionMaker {
ExcHandlerAttr excHandlerAttr = start.get(AType.EXC_HANDLER);
if (excHandlerAttr == null) {
LOG.warn("Missing exception handler attribute for start block");
mth.addWarn("Missing exception handler attribute for start block: " + start);
} else {
handler.getHandlerRegion().addAttr(excHandlerAttr);
}
@@ -27,5 +27,4 @@ public class SelectTypeVisitor {
ArgType newType = ArgType.merge(dex, t, t.selectFirst());
arg.setType(newType);
}
}
@@ -56,11 +56,13 @@ public class TypeInference extends AbstractVisitor {
}
ArgType type = assign.getType();
for (RegisterArg arg : useList) {
ArgType useType = arg.getType();
ArgType newType = ArgType.merge(dex, type, useType);
if (newType != null) {
type = newType;
}
ArgType useType = arg.getType();
if (!type.isTypeKnown() || !useType.isTypeKnown()) {
ArgType newType = ArgType.merge(dex, type, useType);
if (newType != null) {
type = newType;
}
}
}
return type;
}
@@ -0,0 +1,14 @@
package jadx.core.utils;
import jadx.core.codegen.CodeWriter;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.AttrNode;
public class CodegenUtils {
public static void addComments(CodeWriter code, AttrNode node) {
for (String comment : node.getAll(AType.COMMENTS)) {
code.startLine("/* ").add(comment).add(" */");
}
}
}
@@ -74,8 +74,8 @@ public class ErrorsCounter {
return cls.dex().root().getErrorsCounter().addError(cls, errorMsg, e);
}
public static String classError(ClassNode cls, String errorMsg) {
return classError(cls, errorMsg, null);
public static String classWarn(ClassNode cls, String warnMsg) {
return cls.dex().root().getErrorsCounter().addWarning(cls, warnMsg);
}
public static String methodError(MethodNode mth, String errorMsg, Throwable e) {
@@ -149,7 +149,7 @@ public class StringUtils {
}
private static String escapeXmlChar(char c) {
if(c >= 0 && c <= 0x1F) {
if (c >= 0 && c <= 0x1F) {
return "\\" + (int) c;
}
switch (c) {
@@ -5,8 +5,10 @@ import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import jadx.api.JadxDecompiler;
@@ -39,8 +41,16 @@ public class Utils {
if (objects == null) {
return "";
}
return listToString(objects, joiner, Object::toString);
}
public static <T> String listToString(Iterable<T> objects, Function<T, String> toStr) {
return listToString(objects, ", ", toStr);
}
public static <T> String listToString(Iterable<T> objects, String joiner, Function<T, String> toStr) {
StringBuilder sb = new StringBuilder();
listToString(sb, objects, joiner, Object::toString);
listToString(sb, objects, joiner, toStr);
return sb.toString();
}
@@ -91,19 +101,26 @@ public class Utils {
@Override
public void write(int b) {
char c = (char) b;
if (c == '\r') {
// ignore
} else if (c == '\n') {
code.startLine();
} else {
code.add(c);
switch (c) {
case '\n':
code.startLine();
break;
case '\r':
// ignore
break;
default:
code.add(c);
break;
}
}
};
PrintWriter pw = new PrintWriter(w, true);
filterRecursive(throwable);
throwable.printStackTrace(pw);
pw.flush();
try (PrintWriter pw = new PrintWriter(w, true)) {
filterRecursive(throwable);
throwable.printStackTrace(pw);
pw.flush();
}
}
private static void filterRecursive(Throwable th) {
@@ -146,4 +163,16 @@ public class Utils {
}
return new ImmutableList<>(list);
}
public static Map<String, String> newConstStringMap(String... parameters) {
int len = parameters.length;
if (len == 0) {
return Collections.emptyMap();
}
Map<String, String> result = new HashMap<>(len / 2);
for (int i = 0; i < len; i += 2) {
result.put(parameters[i], parameters[i + 1]);
}
return Collections.unmodifiableMap(result);
}
}
@@ -1,16 +1,28 @@
package jadx.core.utils.android;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import com.android.dx.rop.code.AccessFlags;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.core.codegen.ClassGen;
import jadx.core.codegen.CodeWriter;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.FieldInfo;
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.ProcessState;
import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.nodes.parser.FieldInitAttr;
import jadx.core.xmlgen.ResourceStorage;
import jadx.core.xmlgen.entry.ResourceEntry;
/**
* Android resources specific handlers
@@ -21,22 +33,29 @@ public class AndroidResourcesUtils {
private AndroidResourcesUtils() {
}
public static ClassNode searchAppResClass(RootNode root) {
@Nullable
public static ClassNode searchAppResClass(RootNode root, ResourceStorage resStorage) {
String appPackage = root.getAppPackage();
String fullName = appPackage != null ? appPackage + ".R" : "R";
ClassNode resCls = root.searchClassByName(fullName);
if (resCls != null) {
addResourceFields(resCls, resStorage, true);
return resCls;
}
LOG.info("Can't find 'R' class in app package: {}", appPackage);
List<ClassNode> candidates = root.searchClassByShortName("R");
if (candidates.size() == 1) {
return candidates.get(0);
resCls = candidates.get(0);
addResourceFields(resCls, resStorage, true);
return resCls;
}
if (!candidates.isEmpty()) {
LOG.info("Found several 'R' class candidates: {}", candidates);
}
LOG.warn("Unknown 'R' class, create references to '{}'", fullName);
return makeClass(root, fullName);
LOG.info("App 'R' class not found, put all resources ids to : '{}'", fullName);
resCls = makeClass(root, fullName, resStorage);
addResourceFields(resCls, resStorage, false);
return resCls;
}
public static boolean handleAppResField(CodeWriter code, ClassGen clsGen, ClassInfo declClass) {
@@ -50,12 +69,45 @@ public class AndroidResourcesUtils {
return false;
}
private static ClassNode makeClass(RootNode root, String clsName) {
@Nullable
private static ClassNode makeClass(RootNode root, String clsName, ResourceStorage resStorage) {
List<DexNode> dexNodes = root.getDexNodes();
if (dexNodes.isEmpty()) {
return null;
}
ClassInfo r = ClassInfo.fromName(root, clsName);
return new ClassNode(dexNodes.get(0), r);
ClassNode rCls = new ClassNode(dexNodes.get(0), clsName, AccessFlags.ACC_PUBLIC | AccessFlags.ACC_FINAL);
rCls.addAttr(AType.COMMENTS, "This class is generated by JADX");
rCls.setState(ProcessState.PROCESSED);
return rCls;
}
private static void addResourceFields(ClassNode cls, ResourceStorage resStorage, boolean rClsExists) {
Map<String, ClassNode> innerClsMap = new TreeMap<>();
if (rClsExists) {
for (ClassNode innerClass : cls.getInnerClasses()) {
innerClsMap.put(innerClass.getShortName(), innerClass);
}
}
for (ResourceEntry resource : resStorage.getResources()) {
ClassNode typeCls = innerClsMap.computeIfAbsent(resource.getTypeName(), name -> {
ClassNode newTypeCls = new ClassNode(cls.dex(), cls.getFullName() + "$" + name,
AccessFlags.ACC_PUBLIC | AccessFlags.ACC_STATIC | AccessFlags.ACC_FINAL);
cls.addInnerClass(newTypeCls);
if (rClsExists) {
newTypeCls.addAttr(AType.COMMENTS, "added by JADX");
}
return newTypeCls;
});
FieldNode rField = typeCls.searchFieldByName(resource.getKeyName());
if (rField == null) {
FieldInfo rFieldInfo = FieldInfo.from(typeCls.dex(), typeCls.getClassInfo(), resource.getKeyName(), ArgType.INT);
rField = new FieldNode(typeCls, rFieldInfo, AccessFlags.ACC_PUBLIC | AccessFlags.ACC_STATIC | AccessFlags.ACC_FINAL);
rField.addAttr(FieldInitAttr.constValue(resource.getId()));
typeCls.getFields().add(rField);
if (rClsExists) {
rField.addAttr(AType.COMMENTS, "added by JADX");
}
}
}
}
}
@@ -30,5 +30,4 @@ public class CodegenException extends JadxException {
public CodegenException(MethodNode mth, String msg, Throwable th) {
super(mth, msg, th);
}
}
@@ -21,5 +21,4 @@ public class DecodeException extends JadxException {
public DecodeException(MethodNode mth, String msg, Throwable th) {
super(mth, msg, th);
}
}
@@ -23,5 +23,4 @@ public class JadxException extends Exception {
public JadxException(MethodNode mth, String msg, Throwable th) {
super(ErrorsCounter.formatMsg(mth, msg), th);
}
}
@@ -74,7 +74,9 @@ public class InputFile {
}
return;
}
if (skipSources) return;
if (skipSources) {
return;
}
throw new DecodeException("Unsupported input file format: " + file);
}
@@ -24,13 +24,12 @@ public class ZipSecurity {
}
return isInSubDirectoryInternal(baseDir, canonFile.getParentFile());
}
public static boolean isInSubDirectory(File baseDir, File file) {
try {
file = file.getCanonicalFile();
baseDir = baseDir.getCanonicalFile();
}
catch(IOException e) {
} catch (IOException e) {
return false;
}
return isInSubDirectoryInternal(baseDir, file);
@@ -55,7 +55,7 @@ public class BinaryXMLParser extends CommonBinaryParser {
private boolean isOneLine = true;
private int namespaceDepth = 0;
private int[] resourceIds;
private RootNode rootNode;
private String appPackageName;
@@ -180,9 +180,9 @@ public class BinaryXMLParser extends CommonBinaryParser {
int comment = is.readInt32();
int beginPrefix = is.readInt32();
int beginURI = is.readInt32();
String nsValue = getString(beginPrefix);
if(!nsMap.containsValue(nsValue)) {
if (!nsMap.containsValue(nsValue)) {
nsMap.putIfAbsent(getString(beginURI), nsValue);
}
namespaceDepth++;
@@ -200,9 +200,9 @@ public class BinaryXMLParser extends CommonBinaryParser {
int endPrefix = is.readInt32();
int endURI = is.readInt32();
namespaceDepth--;
String nsValue = getString(endPrefix);
if(!nsMap.containsValue(nsValue)) {
if (!nsMap.containsValue(nsValue)) {
nsMap.putIfAbsent(getString(endURI), nsValue);
}
}
@@ -313,7 +313,7 @@ public class BinaryXMLParser extends CommonBinaryParser {
String decodedAttr = ManifestAttributes.getInstance().decode(attrName, attrValData);
if (decodedAttr != null) {
memorizePackageName(attrName, decodedAttr);
if(isDeobfCandidateAttr(shortNsName, attrName)) {
if (isDeobfCandidateAttr(shortNsName, attrName)) {
decodedAttr = deobfClassName(decodedAttr);
}
writer.add(StringUtils.escapeXML(decodedAttr));
@@ -339,16 +339,16 @@ public class BinaryXMLParser extends CommonBinaryParser {
}
return attrName;
}
private String generateNameForNS(String attrUrl) {
for(int i = 1; ; i++) {
for (int i = 1; ; i++) {
String attrName = "ns" + i;
if(!nsMap.containsValue(attrName) && !nsMapGenerated.contains(attrName)) {
if (!nsMap.containsValue(attrName) && !nsMapGenerated.contains(attrName)) {
nsMapGenerated.add(attrName);
// do not add generated value to nsMap
// because attrUrl might be used in a neighbor element, but never defined
writer.add("xmlns:").add(attrName)
.add("=\"").add(attrUrl).add("\" ");
.add("=\"").add(attrUrl).add("\" ");
return attrName;
}
}
@@ -380,8 +380,8 @@ public class BinaryXMLParser extends CommonBinaryParser {
}
private void decodeAttribute(int attributeNS, int attrValDataType, int attrValData,
String shortNsName, String attrName) {
String shortNsName, String attrName) {
if (attrValDataType == TYPE_REFERENCE) {
// reference custom processing
String name = styleMap.get(attrValData);
@@ -419,7 +419,7 @@ public class BinaryXMLParser extends CommonBinaryParser {
} else {
String str = valuesParser.decodeValue(attrValDataType, attrValData);
memorizePackageName(attrName, str);
if(isDeobfCandidateAttr(shortNsName, attrName)) {
if (isDeobfCandidateAttr(shortNsName, attrName)) {
str = deobfClassName(str);
}
writer.add(str != null ? StringUtils.escapeXML(str) : "null");
@@ -454,55 +454,54 @@ public class BinaryXMLParser extends CommonBinaryParser {
writer.decIndent();
}
}
private String getValidTagAttributeName(String originalName) {
if(XMLChar.isValidName(originalName)) {
if (XMLChar.isValidName(originalName)) {
return originalName;
}
if(tagAttrDeobfNames.containsKey(originalName)) {
if (tagAttrDeobfNames.containsKey(originalName)) {
return tagAttrDeobfNames.get(originalName);
}
String generated;
do {
generated = generateTagAttrName();
}
while(tagAttrDeobfNames.containsValue(generated));
while (tagAttrDeobfNames.containsValue(generated));
tagAttrDeobfNames.put(originalName, generated);
return generated;
}
private static String generateTagAttrName() {
final int length = 6;
Random r = new Random();
StringBuilder sb = new StringBuilder();
for(int i = 1; i <= length; i++) {
sb.append((char)(r.nextInt(26) + 'a'));
for (int i = 1; i <= length; i++) {
sb.append((char) (r.nextInt(26) + 'a'));
}
return sb.toString();
}
private String deobfClassName(String className) {
String newName = XmlDeobf.deobfClassName(rootNode, className,
appPackageName);
if(newName != null) {
if (newName != null) {
return newName;
}
return className;
}
private boolean isDeobfCandidateAttr(String shortNsName, String attrName) {
String fullName;
if(shortNsName != null) {
if (shortNsName != null) {
fullName = shortNsName + ":" + attrName;
}
else {
} else {
return false;
}
return "android:name".equals(fullName);
}
private void memorizePackageName(String attrName, String attrValue) {
if("manifest".equals(currentTag) && "package".equals(attrName)) {
if ("manifest".equals(currentTag) && "package".equals(attrName)) {
appPackageName = attrValue;
}
}
@@ -96,5 +96,4 @@ public class CommonBinaryParser extends ParserConstants {
throw new IOException("Decode error: " + message
+ ", position: 0x" + Long.toHexString(is.getPos()));
}
}
@@ -8,7 +8,6 @@ public class ParserConstants {
protected ParserConstants() {
}
protected static final String ANDROID_NS_URL = "http://schemas.android.com/apk/res/android";
/**
@@ -1,70 +1,47 @@
package jadx.core.xmlgen;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.ResourceFile;
import jadx.core.codegen.CodeWriter;
import jadx.core.utils.android.Res9patchStreamDecoder;
import jadx.core.utils.exceptions.JadxRuntimeException;
public class ResContainer implements Comparable<ResContainer> {
private static final Logger LOG = LoggerFactory.getLogger(ResContainer.class);
public enum DataType {
TEXT, DECODED_DATA, RES_LINK, RES_TABLE
}
private final DataType dataType;
private final String name;
private final Object data;
private final List<ResContainer> subFiles;
@Nullable
private CodeWriter content;
@Nullable
private BufferedImage image;
private ResContainer(String name, List<ResContainer> subFiles) {
this.name = name;
this.subFiles = subFiles;
public static ResContainer textResource(String name, CodeWriter content) {
return new ResContainer(name, Collections.emptyList(), content, DataType.TEXT);
}
public static ResContainer singleFile(String name, CodeWriter content) {
ResContainer resContainer = new ResContainer(name, Collections.emptyList());
resContainer.content = content;
return resContainer;
public static ResContainer decodedData(String name, byte[] data) {
return new ResContainer(name, Collections.emptyList(), data, DataType.DECODED_DATA);
}
public static ResContainer singleImageFile(String name, InputStream content) {
ResContainer resContainer = new ResContainer(name, Collections.emptyList());
InputStream newContent = content;
if (name.endsWith(".9.png")) {
Res9patchStreamDecoder decoder = new Res9patchStreamDecoder();
ByteArrayOutputStream os = new ByteArrayOutputStream();
try {
decoder.decode(content, os);
} catch (Exception e) {
LOG.error("Failed to decode 9-patch png image, path: {}", name, e);
}
newContent = new ByteArrayInputStream(os.toByteArray());
}
try {
resContainer.image = ImageIO.read(newContent);
} catch (Exception e) {
throw new JadxRuntimeException("Image load error", e);
}
return resContainer;
public static ResContainer resourceFileLink(ResourceFile resFile) {
return new ResContainer(resFile.getName(), Collections.emptyList(), resFile, DataType.RES_LINK);
}
public static ResContainer multiFile(String name) {
return new ResContainer(name, new ArrayList<>());
public static ResContainer resourceTable(String name, List<ResContainer> subFiles, CodeWriter rootContent) {
return new ResContainer(name, subFiles, rootContent, DataType.RES_TABLE);
}
private ResContainer(String name, List<ResContainer> subFiles, Object data, DataType dataType) {
this.name = Objects.requireNonNull(name);
this.subFiles = Objects.requireNonNull(subFiles);
this.data = Objects.requireNonNull(data);
this.dataType = Objects.requireNonNull(dataType);
}
public String getName() {
@@ -75,24 +52,26 @@ public class ResContainer implements Comparable<ResContainer> {
return name.replace("/", File.separator);
}
@Nullable
public CodeWriter getContent() {
return content;
}
public void setContent(@Nullable CodeWriter content) {
this.content = content;
}
@Nullable
public BufferedImage getImage() {
return image;
}
public List<ResContainer> getSubFiles() {
return subFiles;
}
public DataType getDataType() {
return dataType;
}
public CodeWriter getText() {
return (CodeWriter) data;
}
public byte[] getDecodedData() {
return (byte[]) data;
}
public ResourceFile getResLink() {
return (ResourceFile) data;
}
@Override
public int compareTo(@NotNull ResContainer o) {
return name.compareTo(o.name);
@@ -117,6 +96,6 @@ public class ResContainer implements Comparable<ResContainer> {
@Override
public String toString() {
return "Res{" + name + ", subFiles=" + subFiles + "}";
return "Res{" + name + ", type=" + dataType + ", subFiles=" + subFiles + "}";
}
}

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