Compare commits
68 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8c7140d6b8 | |||
| bf42b97580 | |||
| f8c0449d4e | |||
| b28eaa1a94 | |||
| be509c7104 | |||
| 2931617202 | |||
| 82d0d622a8 | |||
| bcaca781b1 | |||
| ffedaea505 | |||
| aec986447e | |||
| b0e3cfedf4 | |||
| da41efa3db | |||
| 9e0cd2e14e | |||
| d1af751226 | |||
| 618b014b3d | |||
| 7c353a6c6f | |||
| 72b2663949 | |||
| 727285e3df | |||
| a932c7c569 | |||
| 1272ae2d4d | |||
| ddaf0375dc | |||
| f60bb6b121 | |||
| fd3498add6 | |||
| 1ac2cdfc41 | |||
| eadf046b2c | |||
| e9591efd7e | |||
| fbf750f588 | |||
| 63c528dba9 | |||
| 0f27eba1b1 | |||
| a841d0ebe7 | |||
| e0624ce986 | |||
| 7e8435cceb | |||
| 6d59f77165 | |||
| 3a798cb21c | |||
| 1fc92d2a16 | |||
| 850bd96976 | |||
| 20b03aa755 | |||
| 5281eed1a5 | |||
| bedbf94b4a | |||
| 47917fd5c2 | |||
| 0abb51c87a | |||
| 557667b125 | |||
| 1d7bb43dfd | |||
| 6b3e8f083c | |||
| bc629337d6 | |||
| 58993b9799 | |||
| a3464d7184 | |||
| a8a31643f1 | |||
| df9ae295db | |||
| 8c348c935c | |||
| 3815d30fc1 | |||
| 778b9bb851 | |||
| 57dd9e6146 | |||
| 8eef4a9075 | |||
| 87f50ab513 | |||
| 2de86b6db5 | |||
| 9be62fb16e | |||
| f6f883b9d1 | |||
| 5de4d0792f | |||
| 8c43e7f7ce | |||
| 9e24a5abeb | |||
| b587b6d694 | |||
| bc3af8e64d | |||
| 7bd428cf6d | |||
| 912f3c8467 | |||
| a8febb2447 | |||
| 1b0b526822 | |||
| 6250ebdd75 |
@@ -9,6 +9,7 @@ out/
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.attach_pid*
|
||||
|
||||
**/.DS_Store
|
||||
|
||||
|
||||
+3
-2
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
+18
-44
@@ -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;
|
||||
}
|
||||
|
||||
+29
-25
@@ -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
Reference in New Issue
Block a user