Compare commits

..

1 Commits

Author SHA1 Message Date
Skylot 5b8793155c test: enum used in other field init 2024-02-06 18:34:08 +00:00
278 changed files with 2718 additions and 5827 deletions
+8 -8
View File
@@ -20,8 +20,8 @@ jobs:
- name: Set jadx version
run: |
JADX_REV=$(git rev-list --count HEAD)
JADX_VERSION="r${JADX_REV}.${GITHUB_SHA:0:7}"
JADX_LAST_TAG=$(git describe --abbrev=0 --tags)
JADX_VERSION="${JADX_LAST_TAG:1}.$GITHUB_RUN_NUMBER-${GITHUB_SHA:0:8}"
echo "JADX_VERSION=$JADX_VERSION" >> $GITHUB_ENV
- name: Build with Gradle
@@ -37,7 +37,7 @@ jobs:
# Upload unpacked files for now
path: build/jadx/**/*
if-no-files-found: error
retention-days: 14
retention-days: 30
- name: Save exe artifact
uses: actions/upload-artifact@v4
@@ -45,7 +45,7 @@ jobs:
name: ${{ format('jadx-gui-{0}-no-jre-win.exe', env.JADX_VERSION) }}
path: build/*.exe
if-no-files-found: error
retention-days: 14
retention-days: 30
build-win-bundle:
runs-on: windows-latest
@@ -57,7 +57,7 @@ jobs:
- name: Set up JDK
uses: oracle-actions/setup-java@v1
with:
release: 21
release: 17
- name: Print Java version
shell: bash
@@ -66,8 +66,8 @@ jobs:
- name: Set jadx version
shell: bash
run: |
JADX_REV=$(git rev-list --count HEAD)
JADX_VERSION="r${JADX_REV}.${GITHUB_SHA:0:7}"
JADX_LAST_TAG=$(git describe --abbrev=0 --tags)
JADX_VERSION="${JADX_LAST_TAG:1}.$GITHUB_RUN_NUMBER-${GITHUB_SHA:0:8}"
echo "JADX_VERSION=$JADX_VERSION" >> $GITHUB_ENV
- name: Build with Gradle
@@ -81,4 +81,4 @@ jobs:
name: ${{ format('jadx-gui-{0}-with-jre-win', env.JADX_VERSION) }}
path: jadx-gui/build/*-with-jre-win/*
if-no-files-found: error
retention-days: 14
retention-days: 30
@@ -12,4 +12,4 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: gradle/actions/wrapper-validation@v3
- uses: gradle/wrapper-validation-action@v1
+19 -29
View File
@@ -8,19 +8,16 @@
![GitHub release (latest by SemVer)](https://img.shields.io/github/downloads/skylot/jadx/latest/total)
![Latest release](https://img.shields.io/github/release/skylot/jadx.svg)
[![Maven Central](https://img.shields.io/maven-central/v/io.github.skylot/jadx-core)](https://search.maven.org/search?q=g:io.github.skylot%20AND%20jadx)
![Java 11+](https://img.shields.io/badge/Java-11%2B-blue)
[![License](http://img.shields.io/:license-apache-blue.svg)](http://www.apache.org/licenses/LICENSE-2.0.html)
**jadx** - Dex to Java decompiler
Command line and GUI tools for producing Java source code from Android Dex and Apk files
> [!WARNING]
> Please note that in most cases **jadx** can't decompile all 100% of the code, so errors will occur.<br />
> Check [Troubleshooting guide](https://github.com/skylot/jadx/wiki/Troubleshooting-Q&A#decompilation-issues) for workarounds.
:exclamation::exclamation::exclamation: Please note that in most cases **jadx** can't decompile all 100% of the code, so errors will occur. Check [Troubleshooting guide](https://github.com/skylot/jadx/wiki/Troubleshooting-Q&A#decompilation-issues) for workarounds
**Main features:**
- decompile Dalvik bytecode to Java code from APK, dex, aar, aab and zip files
- decompile Dalvik bytecode to java classes from APK, dex, aar, aab and zip files
- decode `AndroidManifest.xml` and other resources from `resources.arsc`
- deobfuscator included
@@ -51,22 +48,18 @@ On Windows run `.bat` files with double-click\
For Windows, you can download it from [oracle.com](https://www.oracle.com/java/technologies/downloads/#jdk17-windows) (select x64 Installer).
### Install
- Arch Linux
[![Arch Linux package](https://img.shields.io/archlinux/v/extra/any/jadx)](https://archlinux.org/packages/extra/any/jadx/)
[![AUR Version](https://img.shields.io/aur/version/jadx-git)](https://aur.archlinux.org/packages/jadx-git)
```bash
sudo pacman -S jadx
```
- macOS
[![homebrew version](https://img.shields.io/homebrew/v/jadx)](https://formulae.brew.sh/formula/jadx)
```bash
brew install jadx
```
- Flathub
[![Flathub Version](https://img.shields.io/flathub/v/com.github.skylot.jadx)](https://flathub.org/apps/com.github.skylot.jadx)
```bash
flatpak install flathub com.github.skylot.jadx
```
1. Arch linux ![Arch Linux package](https://img.shields.io/archlinux/v/extra/any/jadx?label=)
```bash
sudo pacman -S jadx
```
2. macOS ![homebrew version](https://img.shields.io/homebrew/v/jadx?label=)
```bash
brew install jadx
```
3. [Flathub ![Flathub](https://img.shields.io/flathub/v/com.github.skylot.jadx?label=)](https://flathub.org/apps/details/com.github.skylot.jadx)
```bash
flatpak install flathub com.github.skylot.jadx
```
### Use jadx as a library
You can use jadx in your java projects, check details on [wiki page](https://github.com/skylot/jadx/wiki/Use-jadx-as-a-library)
@@ -86,7 +79,7 @@ and also packed to `build/jadx-<version>.zip`
### Usage
```
jadx[-gui] [command] [options] <input files> (.apk, .dex, .jar, .class, .smali, .zip, .aar, .arsc, .aab, .xapk, .jadx.kts)
jadx[-gui] [command] [options] <input files> (.apk, .dex, .jar, .class, .smali, .zip, .aar, .arsc, .aab, .xapk)
commands (use '<command> --help' for command options):
plugins - manage jadx plugins
@@ -128,7 +121,7 @@ options:
--deobf - activate deobfuscation
--deobf-min - min length of name, renamed if shorter, default: 3
--deobf-max - max length of name, renamed if longer, default: 64
--deobf-whitelist - space separated list of classes (full name) and packages (ends with '.*') to exclude from deobfuscation, default: android.support.* android.os.* androidx.core.os.* androidx.annotation.*
--deobf-whitelist - space separated list of classes (full name) and packages (ends with '.*') to exclude from deobfuscation, default: android.support.v4.* android.support.v7.* android.support.v4.os.* android.support.annotation.Px androidx.core.os.* androidx.annotation.Px
--deobf-cfg-file - deobfuscation mappings file used for JADX auto-generated names (in the JOBF file format), default: same dir and name as input file with '.jobf' extension
--deobf-cfg-file-mode - set mode for handling the JADX auto-generated names' deobfuscation map file:
'read' - read if found, don't save (default)
@@ -178,15 +171,12 @@ Plugin options (-P<name>=<value>):
- kotlin-metadata.to-string - rename fields using toString, values: [yes, no], default: yes
- kotlin-metadata.getters - rename simple getters to field names, values: [yes, no], default: yes
4) rename-mappings: various mappings support
- rename-mappings.format - mapping format, values: [AUTO, TINY_FILE, TINY_2_FILE, ENIGMA_FILE, ENIGMA_DIR, SRG_FILE, XSRG_FILE, CSRG_FILE, TSRG_FILE, TSRG_2_FILE, PROGUARD_FILE], default: AUTO
- rename-mappings.invert - invert mapping on load, values: [yes, no], default: no
- rename-mappings.format - mapping format, values: [auto, TINY, TINY_2, ENIGMA, ENIGMA_DIR, MCP, SRG, TSRG, TSRG2, PROGUARD], default: auto
- rename-mappings.invert - invert mapping, values: [yes, no], default: no
Environment variables:
JADX_DISABLE_XML_SECURITY - set to 'true' to disable all security checks for XML files
JADX_DISABLE_ZIP_SECURITY - set to 'true' to disable all security checks for zip files
JADX_ZIP_MAX_ENTRIES_COUNT - maximum allowed number of entries in zip files (default: 100 000)
JADX_CONFIG_DIR - custom config directory, using system by default
JADX_CACHE_DIR - custom cache directory, using system by default
JADX_TMP_DIR - custom temp directory, using system by default
Examples:
@@ -196,7 +186,7 @@ Examples:
jadx --log-level ERROR app.apk
jadx -Pdex-input.verify-checksum=no app.apk
```
These options also work in jadx-gui running from command line and override options from preferences' dialog
These options also worked on jadx-gui running from command line and override options from preferences dialog
### Troubleshooting
Please check wiki page [Troubleshooting Q&A](https://github.com/skylot/jadx/wiki/Troubleshooting-Q&A)
-4
View File
@@ -89,10 +89,6 @@ val copyArtifacts by tasks.registering(Copy::class) {
include("**/*.jar")
rename("jadx-gui-(.*)-all.jar", "jadx-$1-all.jar")
}
from(layout.projectDirectory) {
include("README.md")
include("LICENSE")
}
into(layout.buildDirectory.dir("jadx"))
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
}
+1 -1
View File
@@ -3,7 +3,7 @@ plugins {
}
dependencies {
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.23")
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.22")
}
repositories {
@@ -11,15 +11,15 @@ group = "io.github.skylot"
version = jadxVersion
dependencies {
implementation("org.slf4j:slf4j-api:2.0.13")
implementation("org.slf4j:slf4j-api:2.0.11")
compileOnly("org.jetbrains:annotations:24.1.0")
testImplementation("ch.qos.logback:logback-classic:1.5.6")
testImplementation("ch.qos.logback:logback-classic:1.4.14")
testImplementation("org.hamcrest:hamcrest-library:2.2")
testImplementation("org.mockito:mockito-core:5.11.0")
testImplementation("org.assertj:assertj-core:3.25.3")
testImplementation("org.mockito:mockito-core:5.10.0")
testImplementation("org.assertj:assertj-core:3.25.2")
testImplementation("org.junit.jupiter:junit-jupiter:5.10.2")
testImplementation("org.junit.jupiter:junit-jupiter:5.10.1")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
testCompileOnly("org.jetbrains:annotations:24.1.0")
Binary file not shown.
+2 -2
View File
@@ -1,7 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionSha256Sum=544c35d6bd849ae8a5ed0bcea39ba677dc40f49df7d1835561582da2009b961d
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
distributionSha256Sum=9d926787066a081739e8200858338b4a69e837c3a821a33aca9db09dd4a41026
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Vendored
+10 -10
View File
@@ -43,11 +43,11 @@ set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
@@ -57,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
+1 -2
View File
@@ -18,10 +18,9 @@ dependencies {
runtimeOnly(project(":jadx-plugins:jadx-kotlin-metadata"))
runtimeOnly(project(":jadx-plugins:jadx-script:jadx-script-plugin"))
runtimeOnly(project(":jadx-plugins:jadx-xapk-input"))
runtimeOnly(project(":jadx-plugins:jadx-aab-input"))
implementation("org.jcommander:jcommander:1.83")
implementation("ch.qos.logback:logback-classic:1.5.6")
implementation("ch.qos.logback:logback-classic:1.4.14")
}
application {
@@ -17,6 +17,7 @@ import com.beust.jcommander.ParameterDescription;
import com.beust.jcommander.ParameterException;
import com.beust.jcommander.Parameterized;
import jadx.api.JadxArgs;
import jadx.api.JadxDecompiler;
import jadx.api.plugins.JadxPluginInfo;
import jadx.api.plugins.options.JadxPluginOptions;
@@ -108,11 +109,8 @@ public class JCommanderWrapper<T> {
out.println(appendPluginOptions(maxNamesLen));
out.println();
out.println("Environment variables:");
out.println(" JADX_DISABLE_XML_SECURITY - set to 'true' to disable all security checks for XML files");
out.println(" JADX_DISABLE_ZIP_SECURITY - set to 'true' to disable all security checks for zip files");
out.println(" JADX_ZIP_MAX_ENTRIES_COUNT - maximum allowed number of entries in zip files (default: 100 000)");
out.println(" JADX_CONFIG_DIR - custom config directory, using system by default");
out.println(" JADX_CACHE_DIR - custom cache directory, using system by default");
out.println(" JADX_TMP_DIR - custom temp directory, using system by default");
out.println();
out.println("Examples:");
@@ -167,7 +165,7 @@ public class JCommanderWrapper<T> {
opt.append("- ").append(description);
}
if (addDefaults) {
String defaultValue = getDefaultValue(args, f);
String defaultValue = getDefaultValue(args, f, opt);
if (defaultValue != null && !description.contains("(default)")) {
opt.append(", default: ").append(defaultValue);
}
@@ -190,7 +188,7 @@ public class JCommanderWrapper<T> {
}
@Nullable
private static String getDefaultValue(Object args, Field f) {
private static String getDefaultValue(Object args, Field f, StringBuilder opt) {
try {
Class<?> fieldType = f.getType();
if (fieldType == int.class) {
@@ -221,7 +219,7 @@ public class JCommanderWrapper<T> {
StringBuilder sb = new StringBuilder();
int k = 1;
// load and init all options plugins to print all options
try (JadxDecompiler decompiler = new JadxDecompiler(argsObj.toJadxArgs())) {
try (JadxDecompiler decompiler = new JadxDecompiler(new JadxArgs())) {
JadxPluginManager pluginManager = decompiler.getPluginManager();
pluginManager.load(new JadxExternalPluginsLoader());
pluginManager.initAll();
@@ -29,12 +29,12 @@ import jadx.api.args.IntegerFormat;
import jadx.api.args.ResourceNameSource;
import jadx.api.args.UserRenamesMappingsMode;
import jadx.core.deobf.conditions.DeobfWhitelist;
import jadx.core.utils.exceptions.JadxArgsValidateException;
import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.files.FileUtils;
public class JadxCLIArgs {
@Parameter(description = "<input files> (.apk, .dex, .jar, .class, .smali, .zip, .aar, .arsc, .aab, .xapk, .jadx.kts)")
@Parameter(description = "<input files> (.apk, .dex, .jar, .class, .smali, .zip, .aar, .arsc, .aab, .xapk)")
protected List<String> files = new ArrayList<>(1);
@Parameter(names = { "-d", "--output-dir" }, description = "output directory")
@@ -287,13 +287,14 @@ public class JadxCLIArgs {
System.out.println(JadxDecompiler.getVersion());
return false;
}
if (threadsCount <= 0) {
throw new JadxArgsValidateException("Threads count must be positive, got: " + threadsCount);
}
for (String fileName : files) {
if (fileName.startsWith("-")) {
throw new JadxArgsValidateException("Unknown option: " + fileName);
try {
if (threadsCount <= 0) {
throw new JadxException("Threads count must be positive, got: " + threadsCount);
}
} catch (JadxException e) {
System.err.println("ERROR: " + e.getMessage());
jcw.printUsage();
return false;
}
return true;
}
@@ -558,8 +559,8 @@ public class JadxCLIArgs {
for (String s : value.split(",")) {
try {
set.add(RenameEnum.valueOf(s.trim().toUpperCase(Locale.ROOT)));
} catch (Exception e) {
throw new JadxArgsValidateException(
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException(
'\'' + s + "' is unknown for parameter " + paramName
+ ", possible values are " + enumValuesString(RenameEnum.values()));
}
@@ -624,7 +625,7 @@ public class JadxCLIArgs {
try {
return parse.apply(stringAsEnumName(value));
} catch (Exception e) {
throw new JadxArgsValidateException(
throw new IllegalArgumentException(
'\'' + value + "' is unknown, possible values are: " + enumValuesString(values.get()));
}
}
@@ -7,7 +7,6 @@ import com.beust.jcommander.JCommander;
import jadx.cli.commands.CommandPlugins;
import jadx.cli.commands.ICommand;
import jadx.core.utils.exceptions.JadxArgsValidateException;
public class JadxCLICommands {
private static final Map<String, ICommand> COMMANDS_MAP = new TreeMap<>();
@@ -27,8 +26,7 @@ public class JadxCLICommands {
public static boolean process(JCommanderWrapper<?> jcw, JCommander jc, String parsedCommand) {
ICommand command = COMMANDS_MAP.get(parsedCommand);
if (command == null) {
throw new JadxArgsValidateException("Unknown command: " + parsedCommand
+ ". Expected one of: " + COMMANDS_MAP.keySet());
throw new IllegalArgumentException("Unknown command: " + parsedCommand);
}
JCommander subCommander = jc.getCommands().get(parsedCommand);
command.process(jcw, subCommander);
@@ -12,7 +12,6 @@ import jadx.api.JadxDecompiler;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.visitors.SaveCode;
import jadx.core.utils.exceptions.JadxArgsValidateException;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.utils.files.FileUtils;
@@ -34,10 +33,10 @@ public class SingleClassMode {
.findFirst().orElse(null);
}
if (clsForProcess == null) {
throw new JadxArgsValidateException("Input class not found: " + singleClass);
throw new JadxRuntimeException("Input class not found: " + singleClass);
}
if (clsForProcess.contains(AFlag.DONT_GENERATE)) {
throw new JadxArgsValidateException("Input class can't be saved by current jadx settings (marked as DONT_GENERATE)");
throw new JadxRuntimeException("Input class can't be saved by current jadx settings (marked as DONT_GENERATE)");
}
if (clsForProcess.isInner()) {
clsForProcess = clsForProcess.getTopParentClass();
@@ -53,7 +52,7 @@ public class SingleClassMode {
if (size == 1) {
clsForProcess = classes.get(0);
} else {
throw new JadxArgsValidateException("Found " + size + " classes, single class output can't be used");
throw new JadxRuntimeException("Found " + size + " classes, single class output can't be used");
}
}
ICodeInfo codeInfo;
@@ -23,9 +23,8 @@ public class ConvertToClsSet {
private static final Logger LOG = LoggerFactory.getLogger(ConvertToClsSet.class);
public static void usage() {
LOG.info("<android API level (number)> <output .jcst file> <several input dex or jar files> ");
LOG.info("<output .jcst file> <several input dex or jar files> ");
LOG.info("Arguments to update core.jcst: "
+ "<android API level (number)> "
+ "<jadx root>/jadx-core/src/main/resources/clst/core.jcst "
+ "<sdk_root>/platforms/android-<api level>/android.jar"
+ "<sdk_root>/platforms/android-<api level>/optional/android.car.jar "
@@ -33,12 +32,11 @@ public class ConvertToClsSet {
}
public static void main(String[] args) {
if (args.length != 5) {
if (args.length < 2) {
usage();
System.exit(1);
}
int androidApiLevel = Integer.parseInt(args[0]);
List<Path> inputPaths = Stream.of(args).skip(1).map(Paths::get).collect(Collectors.toList());
List<Path> inputPaths = Stream.of(args).map(Paths::get).collect(Collectors.toList());
Path output = inputPaths.remove(0);
JadxArgs jadxArgs = new JadxArgs();
@@ -59,7 +57,6 @@ public class ConvertToClsSet {
decompiler.load();
RootNode root = decompiler.getRoot();
ClsSet set = new ClsSet(root);
set.setAndroidApiLevel(androidApiLevel);
set.loadFrom(root);
set.save(output);
@@ -20,7 +20,7 @@ import org.slf4j.LoggerFactory;
import jadx.api.JadxArgs;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.android.TextResMapFile;
import jadx.core.xmlgen.ResTableBinaryParser;
import jadx.core.xmlgen.ResTableParser;
/**
* Utility class for convert '.arsc' to simple text file with mapping id to resource name
@@ -54,7 +54,7 @@ public class ConvertArscFile {
rewritesCount = 0;
for (Path resFile : inputPaths) {
LOG.info("Processing {}", resFile);
ResTableBinaryParser resTableParser = new ResTableBinaryParser(root, true);
ResTableParser resTableParser = new ResTableParser(root, true);
if (resFile.getFileName().toString().endsWith(".jar")) {
// Load resources.arsc from android.jar
try (ZipFile zip = new ZipFile(resFile.toFile())) {
@@ -7,7 +7,6 @@ import org.junit.jupiter.api.Test;
import jadx.api.JadxArgs.RenameEnum;
import jadx.cli.JadxCLIArgs.RenameConverter;
import jadx.core.utils.exceptions.JadxArgsValidateException;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -39,7 +38,7 @@ public class RenameConverterTest {
@Test
public void wrong() {
JadxArgsValidateException thrown = assertThrows(JadxArgsValidateException.class,
IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class,
() -> converter.convert("wrong"),
"Expected convert() to throw, but it didn't");
-6
View File
@@ -1,6 +0,0 @@
## jadx app commons
This module contains common utilities used in jadx apps (cli and gui) and not needed in jadx-code module:
- `JadxCommonFiles` - wrapper for `dev.dirs:directories` lib to get
'config' and 'cache' directories in cross-platform way
- `JadxCommonEnv` - utils for work with environment variables
@@ -1,7 +0,0 @@
plugins {
id("jadx-library")
}
dependencies {
implementation("dev.dirs:directories:26")
}
@@ -1,29 +0,0 @@
package jadx.commons.app;
public class JadxCommonEnv {
public static String get(String varName, String defValue) {
String strValue = System.getenv(varName);
return isNullOrEmpty(strValue) ? defValue : strValue;
}
public static boolean getBool(String varName, boolean defValue) {
String strValue = System.getenv(varName);
if (isNullOrEmpty(strValue)) {
return defValue;
}
return strValue.equalsIgnoreCase("true");
}
public static int getInt(String varName, int defValue) {
String strValue = System.getenv(varName);
if (isNullOrEmpty(strValue)) {
return defValue;
}
return Integer.parseInt(strValue);
}
private static boolean isNullOrEmpty(String value) {
return value == null || value.isEmpty();
}
}
@@ -1,73 +0,0 @@
package jadx.commons.app;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import org.jetbrains.annotations.Nullable;
import dev.dirs.ProjectDirectories;
public class JadxCommonFiles {
private static final Path CONFIG_DIR;
private static final Path CACHE_DIR;
public static Path getConfigDir() {
return CONFIG_DIR;
}
public static Path getCacheDir() {
return CACHE_DIR;
}
static {
DirsLoader loader = new DirsLoader();
loader.init();
CONFIG_DIR = loader.getConfigDir();
CACHE_DIR = loader.getCacheDir();
}
private static final class DirsLoader {
private @Nullable ProjectDirectories dirs;
private Path configDir;
private Path cacheDir;
public void init() {
try {
configDir = loadEnvDir("JADX_CONFIG_DIR");
cacheDir = loadEnvDir("JADX_CACHE_DIR");
} catch (Exception e) {
throw new RuntimeException("Failed to init common directories", e);
}
}
private Path loadEnvDir(String envVar) throws IOException {
String envDir = JadxCommonEnv.get(envVar, null);
String dirStr;
if (envDir != null) {
dirStr = envDir;
} else {
dirStr = loadDirs().configDir;
}
Path path = Path.of(dirStr).toAbsolutePath();
Files.createDirectories(path);
return path;
}
private synchronized ProjectDirectories loadDirs() {
if (dirs == null) {
dirs = ProjectDirectories.from("io.github", "skylot", "jadx");
}
return dirs;
}
public Path getCacheDir() {
return cacheDir;
}
public Path getConfigDir() {
return configDir;
}
}
}
+4
View File
@@ -7,6 +7,10 @@ dependencies {
implementation("com.google.code.gson:gson:2.10.1")
// TODO: move resources decoding to separate plugin module
implementation("com.android.tools.build:aapt2-proto:8.2.2-10154469")
implementation("com.google.protobuf:protobuf-java:3.25.2") // forcing latest version
testImplementation("org.apache.commons:commons-lang3:3.14.0")
testImplementation(project(":jadx-plugins:jadx-dex-input"))
@@ -8,6 +8,8 @@ import jadx.api.metadata.ICodeAnnotation;
import jadx.api.metadata.ICodeNodeRef;
public interface ICodeWriter {
String NL = System.getProperty("line.separator");
String INDENT_STR = " ";
boolean isMetadataSupported();
+1 -24
View File
@@ -42,9 +42,6 @@ public class JadxArgs implements Closeable {
public static final int DEFAULT_THREADS_COUNT = Math.max(1, Runtime.getRuntime().availableProcessors() / 2);
public static final String DEFAULT_NEW_LINE_STR = System.lineSeparator();
public static final String DEFAULT_INDENT_STR = " ";
public static final String DEFAULT_OUT_DIR = "jadx-output";
public static final String DEFAULT_SRC_DIR = "sources";
public static final String DEFAULT_RES_DIR = "resources";
@@ -110,7 +107,7 @@ public class JadxArgs implements Closeable {
/**
* List of classes and packages (ends with '.*') to exclude from deobfuscation
*/
private List<String> deobfuscationWhitelist = new ArrayList<>(DeobfWhitelist.DEFAULT_LIST);
private List<String> deobfuscationWhitelist = DeobfWhitelist.DEFAULT_LIST;
/**
* Nodes alias provider for deobfuscator and rename visitor
@@ -147,10 +144,6 @@ public class JadxArgs implements Closeable {
private ICodeData codeData;
private String codeNewLineStr = DEFAULT_NEW_LINE_STR;
private String codeIndentStr = DEFAULT_INDENT_STR;
private CommentsLevel commentsLevel = CommentsLevel.INFO;
private IntegerFormat integerFormat = IntegerFormat.AUTO;
@@ -629,22 +622,6 @@ public class JadxArgs implements Closeable {
this.codeData = codeData;
}
public String getCodeIndentStr() {
return codeIndentStr;
}
public void setCodeIndentStr(String codeIndentStr) {
this.codeIndentStr = codeIndentStr;
}
public String getCodeNewLineStr() {
return codeNewLineStr;
}
public void setCodeNewLineStr(String codeNewLineStr) {
this.codeNewLineStr = codeNewLineStr;
}
public CommentsLevel getCommentsLevel() {
return commentsLevel;
}
@@ -28,6 +28,12 @@ public class JadxArgsValidator {
if (inputFiles.isEmpty() && jadx.getCustomCodeLoaders().isEmpty()) {
throw new JadxArgsValidateException("Please specify input file");
}
for (File inputFile : inputFiles) {
String fileName = inputFile.getName();
if (fileName.startsWith("--")) {
throw new JadxArgsValidateException("Unknown argument: " + fileName);
}
}
for (File file : inputFiles) {
checkFile(file);
}
@@ -52,6 +52,7 @@ import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.utils.files.FileUtils;
import jadx.core.utils.tasks.TaskExecutor;
import jadx.core.xmlgen.BinaryXMLParser;
import jadx.core.xmlgen.ProtoXMLParser;
import jadx.core.xmlgen.ResourcesSaver;
/**
@@ -93,10 +94,10 @@ public final class JadxDecompiler implements Closeable {
private List<ResourceFile> resources;
private BinaryXMLParser binaryXmlParser;
private ProtoXMLParser protoXmlParser;
private final IDecompileScheduler decompileScheduler = new DecompilerScheduler();
private final JadxEventsImpl events = new JadxEventsImpl();
private final ResourcesLoader resourcesLoader = new ResourcesLoader(this);
private final List<ICodeLoader> customCodeLoaders = new ArrayList<>();
private final List<CustomResourcesLoader> customResourcesLoaders = new ArrayList<>();
@@ -118,11 +119,12 @@ public final class JadxDecompiler implements Closeable {
loadInputFiles();
root = new RootNode(args);
root.init();
root.setDecompilerRef(this);
root.mergePasses(customPasses);
root.loadClasses(loadedInputs);
root.initClassPath();
root.loadResources(resourcesLoader, getResources());
root.loadResources(getResources());
root.runPreDecompileStage();
root.initPasses();
loadFinished();
@@ -168,6 +170,7 @@ public final class JadxDecompiler implements Closeable {
classes = null;
resources = null;
binaryXmlParser = null;
protoXmlParser = null;
events.reset();
}
@@ -427,7 +430,7 @@ public final class JadxDecompiler implements Closeable {
if (root == null) {
return Collections.emptyList();
}
resources = resourcesLoader.load(root);
resources = new ResourcesLoader(this).load();
}
return resources;
}
@@ -473,6 +476,13 @@ public final class JadxDecompiler implements Closeable {
return binaryXmlParser;
}
synchronized ProtoXMLParser getProtoXmlParser() {
if (protoXmlParser == null) {
protoXmlParser = new ProtoXMLParser(root);
}
return protoXmlParser;
}
/**
* Get JavaClass by ClassNode without loading and decompilation
*/
@@ -694,10 +704,6 @@ public final class JadxDecompiler implements Closeable {
customPasses.computeIfAbsent(pass.getPassType(), l -> new ArrayList<>()).add(pass);
}
public ResourcesLoader getResourcesLoader() {
return resourcesLoader;
}
@Override
public String toString() {
return "jadx decompiler " + getVersion();
@@ -62,10 +62,6 @@ public class ResourceFile {
return deobfName != null ? deobfName : name;
}
public void setDeobfName(String resFullName) {
this.deobfName = resFullName;
}
public ResourceType getType() {
return type;
}
@@ -88,7 +84,7 @@ public class ResourceFile {
}
String alias = sb.toString();
if (!alias.equals(name)) {
setDeobfName(alias);
deobfName = alias;
return true;
}
return false;
@@ -17,42 +17,30 @@ import org.slf4j.LoggerFactory;
import jadx.api.ResourceFile.ZipRef;
import jadx.api.impl.SimpleCodeInfo;
import jadx.api.plugins.CustomResourcesLoader;
import jadx.api.plugins.resources.IResContainerFactory;
import jadx.api.plugins.resources.IResTableParserProvider;
import jadx.api.plugins.resources.IResourcesLoader;
import jadx.api.plugins.utils.ZipSecurity;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.Utils;
import jadx.core.utils.android.Res9patchStreamDecoder;
import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.utils.files.FileUtils;
import jadx.core.xmlgen.BinaryXMLParser;
import jadx.core.xmlgen.IResTableParser;
import jadx.core.xmlgen.ResContainer;
import jadx.core.xmlgen.ResTableBinaryParserProvider;
import jadx.core.xmlgen.ResProtoParser;
import jadx.core.xmlgen.ResTableParser;
import static jadx.core.utils.files.FileUtils.READ_BUFFER_SIZE;
import static jadx.core.utils.files.FileUtils.copyStream;
// TODO: move to core package
public final class ResourcesLoader implements IResourcesLoader {
public final class ResourcesLoader {
private static final Logger LOG = LoggerFactory.getLogger(ResourcesLoader.class);
private final JadxDecompiler jadxRef;
private final List<IResTableParserProvider> resTableParserProviders = new ArrayList<>();
private final List<IResContainerFactory> resContainerFactories = new ArrayList<>();
private BinaryXMLParser binaryXmlParser;
ResourcesLoader(JadxDecompiler jadxRef) {
this.jadxRef = jadxRef;
this.resTableParserProviders.add(new ResTableBinaryParserProvider());
}
List<ResourceFile> load(RootNode root) {
init(root);
List<ResourceFile> load() {
List<File> inputFiles = jadxRef.getArgs().getInputFiles();
List<ResourceFile> list = new ArrayList<>(inputFiles.size());
for (File file : inputFiles) {
@@ -61,37 +49,10 @@ public final class ResourcesLoader implements IResourcesLoader {
return list;
}
private void init(RootNode root) {
for (IResTableParserProvider resTableParserProvider : resTableParserProviders) {
try {
resTableParserProvider.init(root);
} catch (Exception e) {
throw new JadxRuntimeException("Failed to init res table provider: " + resTableParserProvider);
}
}
for (IResContainerFactory resContainerFactory : resContainerFactories) {
try {
resContainerFactory.init(root);
} catch (Exception e) {
throw new JadxRuntimeException("Failed to init res container factory: " + resContainerFactory);
}
}
}
public interface ResourceDecoder<T> {
T decode(long size, InputStream is) throws IOException;
}
@Override
public void addResContainerFactory(IResContainerFactory resContainerFactory) {
resContainerFactories.add(resContainerFactory);
}
@Override
public void addResTableParserProvider(IResTableParserProvider resTableParserProvider) {
resTableParserProviders.add(resTableParserProvider);
}
public static <T> T decodeStream(ResourceFile rf, ResourceDecoder<T> decoder) throws JadxException {
try {
ZipRef zipRef = rf.getZipRef();
@@ -121,8 +82,7 @@ public final class ResourcesLoader implements IResourcesLoader {
static ResContainer loadContent(JadxDecompiler jadxRef, ResourceFile rf) {
try {
ResourcesLoader resLoader = jadxRef.getResourcesLoader();
return decodeStream(rf, (size, is) -> resLoader.loadContent(rf, is));
return decodeStream(rf, (size, is) -> loadContent(jadxRef, rf, is));
} catch (JadxException e) {
LOG.error("Decode error", e);
ICodeWriter cw = jadxRef.getRoot().makeCodeWriter();
@@ -132,48 +92,36 @@ public final class ResourcesLoader implements IResourcesLoader {
}
}
private ResContainer loadContent(ResourceFile resFile, InputStream inputStream) throws IOException {
for (IResContainerFactory customFactory : resContainerFactories) {
ResContainer resContainer = customFactory.create(resFile, inputStream);
if (resContainer != null) {
return resContainer;
}
}
switch (resFile.getType()) {
private static ResContainer loadContent(JadxDecompiler jadxRef, ResourceFile rf,
InputStream inputStream) throws IOException {
RootNode root = jadxRef.getRoot();
switch (rf.getType()) {
case MANIFEST:
case XML:
ICodeInfo content = loadBinaryXmlParser().parse(inputStream);
return ResContainer.textResource(resFile.getDeobfName(), content);
case XML: {
ICodeInfo content;
if (root.isProto()) {
content = jadxRef.getProtoXmlParser().parse(inputStream);
} else {
content = jadxRef.getBinaryXmlParser().parse(inputStream);
}
return ResContainer.textResource(rf.getDeobfName(), content);
}
case ARSC:
return decodeTable(resFile, inputStream).decodeFiles();
if (root.isProto()) {
return new ResProtoParser(root).decodeFiles(inputStream);
} else {
return new ResTableParser(root).decodeFiles(inputStream);
}
case IMG:
return decodeImage(resFile, inputStream);
return decodeImage(rf, inputStream);
default:
return ResContainer.resourceFileLink(resFile);
return ResContainer.resourceFileLink(rf);
}
}
public IResTableParser decodeTable(ResourceFile resFile, InputStream is) throws IOException {
if (resFile.getType() != ResourceType.ARSC) {
throw new IllegalArgumentException("Unexpected resource type for decode: " + resFile.getType() + ", expect '.pb'/'.arsc'");
}
IResTableParser parser = null;
for (IResTableParserProvider provider : resTableParserProviders) {
parser = provider.getParser(resFile);
if (parser != null) {
break;
}
}
if (parser == null) {
throw new JadxRuntimeException("Unknown type of resource file: " + resFile.getOriginalName());
}
parser.decode(is);
return parser;
}
private static ResContainer decodeImage(ResourceFile rf, InputStream inputStream) {
String name = rf.getOriginalName();
if (name.endsWith(".9.png")) {
@@ -236,11 +184,4 @@ public final class ResourcesLoader implements IResourcesLoader {
copyStream(is, baos);
return new SimpleCodeInfo(baos.toString("UTF-8"));
}
private synchronized BinaryXMLParser loadBinaryXmlParser() {
if (binaryXmlParser == null) {
binaryXmlParser = new BinaryXMLParser(jadxRef.getRoot());
}
return binaryXmlParser;
}
}
@@ -1,69 +0,0 @@
package jadx.api.data;
public enum CommentStyle {
/**
* <pre>
* // comment
* </pre>
*/
LINE("// ", "// ", ""),
// @formatter:off
/**
* <pre>
* /*
* * comment
* *&#47;
* </pre>
*/
// @formatter:on
BLOCK("/*\n * ", " * ", "\n */"),
/**
* <pre>
* /* comment *&#47;
* </pre>
*/
BLOCK_CONDENSED("/* ", " * ", " */"),
// @formatter:off
/**
* <pre>
* /**
* * comment
* *&#47;
* </pre>
*/
// @formatter:on
JAVADOC("/**\n * ", " * ", "\n */"),
/**
* <pre>
* /** comment *&#47;
* </pre>
*/
JAVADOC_CONDENSED("/** ", " * ", " */");
private final String start;
private final String onNewLine;
private final String end;
CommentStyle(String start, String onNewLine, String end) {
this.start = start;
this.onNewLine = onNewLine;
this.end = end;
}
public String getStart() {
return start;
}
public String getOnNewLine() {
return onNewLine;
}
public String getEnd() {
return end;
}
}
@@ -10,6 +10,4 @@ public interface ICodeComment extends Comparable<ICodeComment> {
IJavaCodeRef getCodeRef();
String getComment();
CommentStyle getStyle();
}
@@ -3,7 +3,6 @@ package jadx.api.data.impl;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import jadx.api.data.CommentStyle;
import jadx.api.data.ICodeComment;
import jadx.api.data.IJavaCodeRef;
import jadx.api.data.IJavaNodeRef;
@@ -14,25 +13,15 @@ public class JadxCodeComment implements ICodeComment {
@Nullable
private IJavaCodeRef codeRef;
private String comment;
private CommentStyle style = CommentStyle.LINE;
public JadxCodeComment(IJavaNodeRef nodeRef, String comment) {
this(nodeRef, null, comment);
}
public JadxCodeComment(IJavaNodeRef nodeRef, String comment, CommentStyle style) {
this(nodeRef, null, comment, style);
}
public JadxCodeComment(IJavaNodeRef nodeRef, @Nullable IJavaCodeRef codeRef, String comment) {
this(nodeRef, codeRef, comment, CommentStyle.LINE);
}
public JadxCodeComment(IJavaNodeRef nodeRef, @Nullable IJavaCodeRef codeRef, String comment, CommentStyle style) {
this.nodeRef = nodeRef;
this.codeRef = codeRef;
this.comment = comment;
this.style = style;
}
public JadxCodeComment() {
@@ -67,15 +56,6 @@ public class JadxCodeComment implements ICodeComment {
this.comment = comment;
}
@Override
public CommentStyle getStyle() {
return style;
}
public void setStyle(CommentStyle style) {
this.style = style;
}
@Override
public int compareTo(@NotNull ICodeComment other) {
int cmpNodeRef = this.getNodeRef().compareTo(other.getNodeRef());
@@ -93,7 +73,6 @@ public class JadxCodeComment implements ICodeComment {
return "JadxCodeComment{" + nodeRef
+ ", ref=" + codeRef
+ ", comment='" + comment + '\''
+ ", style=" + style
+ '}';
}
}
@@ -21,6 +21,9 @@ public class AnnotatedCodeWriter extends SimpleCodeWriter implements ICodeWriter
private Map<Integer, ICodeAnnotation> annotations = Collections.emptyMap();
private Map<Integer, Integer> lineMap = Collections.emptyMap();
public AnnotatedCodeWriter() {
}
public AnnotatedCodeWriter(JadxArgs args) {
super(args);
}
@@ -32,9 +35,9 @@ public class AnnotatedCodeWriter extends SimpleCodeWriter implements ICodeWriter
@Override
public AnnotatedCodeWriter addMultiLine(String str) {
if (str.contains(newLineStr)) {
buf.append(str.replace(newLineStr, newLineStr + indentStr));
line += StringUtils.countMatches(str, newLineStr);
if (str.contains(NL)) {
buf.append(str.replace(NL, NL + indentStr));
line += StringUtils.countMatches(str, NL);
offset = 0;
} else {
buf.append(str);
@@ -81,7 +84,7 @@ public class AnnotatedCodeWriter extends SimpleCodeWriter implements ICodeWriter
@Override
protected void addLine() {
buf.append(newLineStr);
buf.append(NL);
line++;
offset = 0;
}
@@ -151,6 +154,7 @@ public class AnnotatedCodeWriter extends SimpleCodeWriter implements ICodeWriter
@Override
public ICodeInfo finish() {
processDefinitionAnnotations();
validateAnnotations();
String code = buf.toString();
buf = null;
@@ -162,6 +166,18 @@ public class AnnotatedCodeWriter extends SimpleCodeWriter implements ICodeWriter
return annotations;
}
private void processDefinitionAnnotations() {
if (!annotations.isEmpty()) {
annotations.forEach((k, v) -> {
if (v instanceof NodeDeclareRef) {
NodeDeclareRef declareRef = (NodeDeclareRef) v;
declareRef.setDefPos(k);
declareRef.getNode().setDefPosition(k);
}
});
}
}
private void validateAnnotations() {
if (annotations.isEmpty()) {
return;
@@ -14,39 +14,38 @@ import jadx.api.metadata.ICodeNodeRef;
import jadx.core.utils.Utils;
/**
* CodeWriter implementation without meta information support
* CodeWriter implementation without meta information support (only strings builder)
*/
public class SimpleCodeWriter implements ICodeWriter {
private static final Logger LOG = LoggerFactory.getLogger(SimpleCodeWriter.class);
private static final String[] INDENT_CACHE = {
"",
INDENT_STR,
INDENT_STR + INDENT_STR,
INDENT_STR + INDENT_STR + INDENT_STR,
INDENT_STR + INDENT_STR + INDENT_STR + INDENT_STR,
INDENT_STR + INDENT_STR + INDENT_STR + INDENT_STR + INDENT_STR,
};
protected StringBuilder buf = new StringBuilder();
protected String indentStr = "";
protected int indent = 0;
protected final boolean insertLineNumbers;
protected final String singleIndentStr;
protected final String newLineStr;
private final boolean insertLineNumbers;
public SimpleCodeWriter() {
this.insertLineNumbers = false;
}
public SimpleCodeWriter(JadxArgs args) {
this.insertLineNumbers = args.isInsertDebugLines();
this.singleIndentStr = args.getCodeIndentStr();
this.newLineStr = args.getCodeNewLineStr();
if (insertLineNumbers) {
incIndent(3);
add(indentStr);
}
}
/**
* Constructor with JadxArgs should be used.
*/
@Deprecated
public SimpleCodeWriter() {
this.insertLineNumbers = false;
this.singleIndentStr = JadxArgs.DEFAULT_INDENT_STR;
this.newLineStr = JadxArgs.DEFAULT_NEW_LINE_STR;
}
@Override
public boolean isMetadataSupported() {
return false;
@@ -97,8 +96,8 @@ public class SimpleCodeWriter implements ICodeWriter {
@Override
public SimpleCodeWriter addMultiLine(String str) {
if (str.contains(newLineStr)) {
buf.append(str.replace(newLineStr, newLineStr + indentStr));
if (str.contains(NL)) {
buf.append(str.replace(NL, NL + indentStr));
} else {
buf.append(str);
}
@@ -131,12 +130,12 @@ public class SimpleCodeWriter implements ICodeWriter {
@Override
public SimpleCodeWriter addIndent() {
add(singleIndentStr);
add(INDENT_STR);
return this;
}
protected void addLine() {
buf.append(newLineStr);
buf.append(NL);
}
protected SimpleCodeWriter addLineIndent() {
@@ -145,7 +144,12 @@ public class SimpleCodeWriter implements ICodeWriter {
}
private void updateIndent() {
this.indentStr = Utils.strRepeat(singleIndentStr, indent);
int curIndent = indent;
if (curIndent < INDENT_CACHE.length) {
this.indentStr = INDENT_CACHE[curIndent];
} else {
this.indentStr = Utils.strRepeat(INDENT_STR, curIndent);
}
}
@Override
@@ -215,17 +219,17 @@ public class SimpleCodeWriter implements ICodeWriter {
@Override
public ICodeInfo finish() {
String code = getStringWithoutFirstEmptyLine();
removeFirstEmptyLine();
String code = buf.toString();
buf = null;
return new SimpleCodeInfo(code);
}
private String getStringWithoutFirstEmptyLine() {
int len = newLineStr.length();
if (buf.length() > len && buf.substring(0, len).equals(newLineStr)) {
return buf.substring(len);
protected void removeFirstEmptyLine() {
int len = NL.length();
if (buf.length() > len && buf.substring(0, len).equals(NL)) {
buf.delete(0, len);
}
return buf.toString();
}
@Override
@@ -12,7 +12,6 @@ import jadx.api.plugins.gui.JadxGuiContext;
import jadx.api.plugins.input.JadxCodeInput;
import jadx.api.plugins.options.JadxPluginOptions;
import jadx.api.plugins.pass.JadxPass;
import jadx.api.plugins.resources.IResourcesLoader;
public interface JadxPluginContext {
@@ -33,11 +32,6 @@ public interface JadxPluginContext {
*/
void registerInputsHashSupplier(Supplier<String> supplier);
/**
* Customize resource loading
*/
IResourcesLoader getResourcesLoader();
/**
* Access to jadx-gui specific methods
*/
@@ -12,12 +12,12 @@ public enum OptionFlag {
HIDE_IN_GUI,
/**
* Option will be read-only in jadx-gui (can be used for calculated properties)
* Do not show this option in jadx-gui (useful if option is configured with custom ui)
*/
DISABLE_IN_GUI,
/**
* Add this flag only if the option does not affect generated code.
* Add this flag only if option do not affect generated code.
* If added, option value change will not cause code cache reset.
*/
NOT_CHANGING_CODE,
@@ -64,14 +64,6 @@ public abstract class BasePluginOptionsBuilder implements JadxPluginOptions {
.parser(v -> v));
}
public OptionBuilder<Integer> intOption(String name) {
return addOption(
new OptionData<Integer>(name)
.type(OptionType.NUMBER)
.formatter(Object::toString)
.parser(Integer::parseInt));
}
public <E extends Enum<?>> OptionBuilder<E> enumOption(String name, E[] values, Function<String, E> valueOf) {
return addOption(
new OptionData<E>(name)
@@ -1,33 +0,0 @@
package jadx.api.plugins.resources;
import java.io.IOException;
import java.io.InputStream;
import org.jetbrains.annotations.Nullable;
import jadx.api.ResourceFile;
import jadx.core.dex.nodes.RootNode;
import jadx.core.xmlgen.ResContainer;
/**
* Factory for {@link ResContainer}. Can be used in plugins via
* {@code context.getResourcesLoader().addResContainerFactory()} to implement content parsing in
* files with
* different formats.
*/
public interface IResContainerFactory {
/**
* Optional init method
*/
default void init(RootNode root) {
}
/**
* Checks if resource file is of expected format and tries to parse its content.
*
* @return {@link ResContainer} if file is of expected format, {@code null} otherwise.
*/
@Nullable
ResContainer create(ResourceFile resFile, InputStream inputStream) throws IOException;
}
@@ -1,30 +0,0 @@
package jadx.api.plugins.resources;
import org.jetbrains.annotations.Nullable;
import jadx.api.ResourceFile;
import jadx.core.dex.nodes.RootNode;
import jadx.core.xmlgen.IResTableParser;
/**
* Provides the resource table parser instance for specific resource table file format. Can be used
* in plugins via {@code context.getResourcesLoader().addResTableParserProvider()} to parse
* resources from tables
* in different formats.
*/
public interface IResTableParserProvider {
/**
* Optional init method
*/
default void init(RootNode root) {
}
/**
* Checks a file format and provides the instance if the format is expected.
*
* @return {@link IResTableParser} if resource table is of expected format, {@code null} otherwise.
*/
@Nullable
IResTableParser getParser(ResourceFile resFile);
}
@@ -1,8 +0,0 @@
package jadx.api.plugins.resources;
public interface IResourcesLoader {
void addResContainerFactory(IResContainerFactory resContainerFactory);
void addResTableParserProvider(IResTableParserProvider resTableParserProvider);
}
@@ -1,5 +1,7 @@
package jadx.api.utils;
import jadx.api.ICodeWriter;
public class CodeUtils {
public static String getLineForPos(String code, int pos) {
@@ -9,32 +11,18 @@ public class CodeUtils {
}
public static int getLineStartForPos(String code, int pos) {
int start = getNewLinePosBefore(code, pos);
return start == -1 ? 0 : start + 1;
String newLine = ICodeWriter.NL;
int start = code.lastIndexOf(newLine, pos);
return start == -1 ? 0 : start + newLine.length();
}
public static int getLineEndForPos(String code, int pos) {
int end = getNewLinePosAfter(code, pos);
int end = code.indexOf(ICodeWriter.NL, pos);
return end == -1 ? code.length() : end;
}
public static int getNewLinePosAfter(String code, int startPos) {
int pos = code.indexOf('\n', startPos);
if (pos != -1) {
// check for '\r\n'
int prev = pos - 1;
if (code.charAt(prev) == '\r') {
return prev;
}
}
return pos;
}
public static int getNewLinePosBefore(String code, int startPos) {
return code.lastIndexOf('\n', startPos);
}
public static int getLineNumForPos(String code, int pos, String newLine) {
public static int getLineNumForPos(String code, int pos) {
String newLine = ICodeWriter.NL;
int newLineLen = newLine.length();
int line = 1;
int prev = 0;
@@ -13,7 +13,6 @@ import org.slf4j.LoggerFactory;
import jadx.api.CommentsLevel;
import jadx.api.JadxArgs;
import jadx.core.deobf.DeobfuscatorVisitor;
import jadx.core.deobf.InitRenameProviders;
import jadx.core.deobf.SaveDeobfMapping;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.visitors.AnonymousClassVisitor;
@@ -55,8 +54,6 @@ import jadx.core.dex.visitors.debuginfo.DebugInfoApplyVisitor;
import jadx.core.dex.visitors.debuginfo.DebugInfoAttachVisitor;
import jadx.core.dex.visitors.finaly.MarkFinallyVisitor;
import jadx.core.dex.visitors.kotlin.ProcessKotlinInternals;
import jadx.core.dex.visitors.prepare.AddAndroidConstants;
import jadx.core.dex.visitors.prepare.CollectConstValues;
import jadx.core.dex.visitors.regions.CheckRegions;
import jadx.core.dex.visitors.regions.CleanRegions;
import jadx.core.dex.visitors.regions.IfRegionVisitor;
@@ -70,7 +67,6 @@ import jadx.core.dex.visitors.rename.SourceFileRename;
import jadx.core.dex.visitors.shrink.CodeShrinkVisitor;
import jadx.core.dex.visitors.ssa.SSATransform;
import jadx.core.dex.visitors.typeinference.FinishTypeInference;
import jadx.core.dex.visitors.typeinference.FixTypesVisitor;
import jadx.core.dex.visitors.typeinference.TypeInferenceVisitor;
import jadx.core.dex.visitors.usage.UsageInfoVisitor;
import jadx.core.utils.exceptions.JadxRuntimeException;
@@ -99,11 +95,8 @@ public class Jadx {
List<IDexTreeVisitor> passes = new ArrayList<>();
passes.add(new SignatureProcessor());
passes.add(new OverrideMethodVisitor());
passes.add(new AddAndroidConstants());
passes.add(new CollectConstValues());
// rename and deobfuscation
passes.add(new InitRenameProviders());
passes.add(new DeobfuscatorVisitor());
passes.add(new SourceFileRename());
passes.add(new RenameVisitor());
@@ -148,9 +141,7 @@ public class Jadx {
if (args.isDebugInfo()) {
passes.add(new DebugInfoApplyVisitor());
}
passes.add(new FixTypesVisitor());
passes.add(new FinishTypeInference());
if (args.getUseKotlinMethodsForVarNames() != JadxArgs.UseKotlinMethodsForVarNames.DISABLE) {
passes.add(new ProcessKotlinInternals());
}
@@ -225,7 +216,6 @@ public class Jadx {
if (args.isDebugInfo()) {
passes.add(new DebugInfoApplyVisitor());
}
passes.add(new FixTypesVisitor());
passes.add(new FinishTypeInference());
passes.add(new CodeRenameVisitor());
passes.add(new DeboxingVisitor());
@@ -44,17 +44,14 @@ public class ClsSet {
private static final String CLST_PATH = "/clst/" + CLST_FILENAME;
private static final String JADX_CLS_SET_HEADER = "jadx-cst";
private static final int VERSION = 5;
private static final int VERSION = 4;
private static final String STRING_CHARSET = "US-ASCII";
private static final ArgType[] EMPTY_ARGTYPE_ARRAY = new ArgType[0];
private static final ArgType[] OBJECT_ARGTYPE_ARRAY = new ArgType[] { ArgType.OBJECT };
private final RootNode root;
private int androidApiLevel;
public ClsSet(RootNode root) {
this.root = root;
}
@@ -82,8 +79,7 @@ public class ClsSet {
if (LOG.isDebugEnabled()) {
long time = System.currentTimeMillis() - startTime;
int methodsCount = Stream.of(classes).mapToInt(clspClass -> clspClass.getMethodsMap().size()).sum();
LOG.debug("Clst file loaded in {}ms, android api: {}, classes: {}, methods: {}",
time, androidApiLevel, classes.length, methodsCount);
LOG.debug("Clst file loaded in {}ms, classes: {}, methods: {}", time, classes.length, methodsCount);
}
}
@@ -97,7 +93,7 @@ public class ClsSet {
cls.load();
ClspClassSource source = getClspClassSource(cls);
ClspClass nClass = new ClspClass(clsType, k, cls.getAccessFlags().rawValue(), source);
ClspClass nClass = new ClspClass(clsType, k, source);
if (names.put(clsRawName, nClass) != null) {
throw new JadxRuntimeException("Duplicate class: " + clsRawName);
}
@@ -155,11 +151,7 @@ public class ClsSet {
// cls is java.lang.Object
return EMPTY_ARGTYPE_ARRAY;
}
int interfacesCount = cls.getInterfaces().size();
if (interfacesCount == 0 && superClass == ArgType.OBJECT) {
return OBJECT_ARGTYPE_ARRAY;
}
ArgType[] parents = new ArgType[1 + interfacesCount];
ArgType[] parents = new ArgType[1 + cls.getInterfaces().size()];
parents[0] = superClass;
int k = 1;
for (ArgType iface : cls.getInterfaces()) {
@@ -201,12 +193,10 @@ public class ClsSet {
DataOutputStream out = new DataOutputStream(output);
out.writeBytes(JADX_CLS_SET_HEADER);
out.writeByte(VERSION);
out.writeInt(androidApiLevel);
Map<String, ClspClass> names = new HashMap<>(classes.length);
out.writeInt(classes.length);
for (ClspClass cls : classes) {
out.writeInt(cls.getAccFlags());
writeUnsignedByte(out, cls.getSource().ordinal());
String clsName = cls.getName();
writeString(out, clsName);
@@ -253,10 +243,6 @@ public class ClsSet {
out.writeByte(-1);
return;
}
if (arr == OBJECT_ARGTYPE_ARRAY) {
out.writeByte(-2);
return;
}
int size = arr.length;
out.writeByte(size);
if (size != 0) {
@@ -308,22 +294,22 @@ public class ClsSet {
try (DataInputStream in = new DataInputStream(new BufferedInputStream(input))) {
byte[] header = new byte[JADX_CLS_SET_HEADER.length()];
int readHeaderLength = in.read(header);
int version = in.readByte();
if (readHeaderLength != JADX_CLS_SET_HEADER.length()
|| !JADX_CLS_SET_HEADER.equals(new String(header, STRING_CHARSET))) {
|| !JADX_CLS_SET_HEADER.equals(new String(header, STRING_CHARSET))
|| version != VERSION) {
throw new DecodeException("Wrong jadx class set header");
}
int version = in.readByte();
if (version != VERSION) {
throw new DecodeException("Wrong jadx class set version, got: " + version + ", expect: " + VERSION);
}
androidApiLevel = in.readInt();
int clsCount = in.readInt();
classes = new ClspClass[clsCount];
ClspClassSource[] clspClassSources = ClspClassSource.values();
for (int i = 0; i < clsCount; i++) {
int accFlags = in.readInt();
ClspClassSource clsSource = readClsSource(in);
int source = readUnsignedByte(in);
if (source < 0 || source > clspClassSources.length) {
throw new DecodeException("Wrong jadx source identifier");
}
String name = readString(in);
classes[i] = new ClspClass(ArgType.object(name), i, accFlags, clsSource);
classes[i] = new ClspClass(ArgType.object(name), i, clspClassSources[source]);
}
for (int i = 0; i < clsCount; i++) {
ClspClass nClass = classes[i];
@@ -335,15 +321,6 @@ public class ClsSet {
}
}
private static ClspClassSource readClsSource(DataInputStream in) throws IOException, DecodeException {
int source = readUnsignedByte(in);
ClspClassSource[] clspClassSources = ClspClassSource.values();
if (source < 0 || source > clspClassSources.length) {
throw new DecodeException("Wrong jadx source identifier: " + source);
}
return clspClassSources[source];
}
private List<ClspMethod> readClsMethods(DataInputStream in, ClassInfo clsInfo) throws IOException {
int mCount = in.readShort();
List<ClspMethod> methods = new ArrayList<>(mCount);
@@ -389,20 +366,17 @@ public class ClsSet {
@Nullable
private ArgType[] readArgTypesArray(DataInputStream in) throws IOException {
int count = in.readByte();
switch (count) {
case -1:
return null;
case -2:
return OBJECT_ARGTYPE_ARRAY;
case 0:
return EMPTY_ARGTYPE_ARRAY;
default:
ArgType[] arr = new ArgType[count];
for (int i = 0; i < count; i++) {
arr[i] = readArgType(in);
}
return arr;
if (count == -1) {
return null;
}
if (count == 0) {
return EMPTY_ARGTYPE_ARRAY;
}
ArgType[] arr = new ArgType[count];
for (int i = 0; i < count; i++) {
arr[i] = readArgType(in);
}
return arr;
}
private ArgType readArgType(DataInputStream in) throws IOException {
@@ -410,6 +384,9 @@ public class ClsSet {
if (ordinal == -1) {
return null;
}
if (ordinal >= TypeEnum.values().length) {
throw new JadxRuntimeException("Incorrect ordinal for type enum: " + ordinal);
}
switch (TypeEnum.values()[ordinal]) {
case WILDCARD:
ArgType.WildcardBound bound = ArgType.WildcardBound.getByNum(in.readByte());
@@ -437,7 +414,7 @@ public class ClsSet {
return classes[in.readInt()].getClsType();
case ARRAY:
return ArgType.array(Objects.requireNonNull(readArgType(in)));
return ArgType.array(readArgType(in));
case PRIMITIVE:
char shortName = (char) in.readByte();
@@ -497,12 +474,4 @@ public class ClsSet {
nameMap.put(cls.getName(), cls);
}
}
public int getAndroidApiLevel() {
return androidApiLevel;
}
public void setAndroidApiLevel(int androidApiLevel) {
this.androidApiLevel = androidApiLevel;
}
}
@@ -7,9 +7,6 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.intellij.lang.annotations.MagicConstant;
import jadx.api.plugins.input.data.AccessFlags;
import jadx.core.dex.instructions.args.ArgType;
/**
@@ -19,17 +16,21 @@ public class ClspClass {
private final ArgType clsType;
private final int id;
private final int accFlags;
private ArgType[] parents;
private Map<String, ClspMethod> methodsMap = Collections.emptyMap();
private List<ArgType> typeParameters = Collections.emptyList();
private ClspClassSource source;
public ClspClass(ArgType clsType, int id, int accFlags, ClspClassSource source) {
public ClspClass(ArgType clsType, int id) {
this.clsType = clsType;
this.id = id;
this.source = ClspClassSource.APP;
}
public ClspClass(ArgType clsType, int id, ClspClassSource source) {
this.clsType = clsType;
this.id = id;
this.accFlags = accFlags;
this.source = source;
}
@@ -45,18 +46,6 @@ public class ClspClass {
return id;
}
public int getAccFlags() {
return accFlags;
}
public boolean isInterface() {
return AccessFlags.hasFlag(accFlags, AccessFlags.INTERFACE);
}
public boolean hasAccFlag(@MagicConstant(flagsFromClass = AccessFlags.class) int flags) {
return AccessFlags.hasFlag(accFlags, flags);
}
public ArgType[] getParents() {
return parents;
}
@@ -13,7 +13,6 @@ import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.core.Consts;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.ClassNode;
@@ -107,7 +106,7 @@ public class ClspGraph {
private void addClass(ClassNode cls) {
ArgType clsType = cls.getClassInfo().getType();
String rawName = clsType.getObject();
ClspClass clspClass = new ClspClass(clsType, -1, cls.getAccessFlags().rawValue(), ClspClassSource.APP);
ClspClass clspClass = new ClspClass(clsType, -1);
clspClass.setParents(ClsSet.makeParentsArray(cls));
nameMap.put(rawName, clspClass);
}
@@ -175,8 +174,6 @@ public class ClspGraph {
return result == null ? Collections.emptySet() : result;
}
private static final Set<String> OBJECT_SINGLE_SET = Collections.singleton(Consts.CLASS_OBJECT);
private void fillSuperTypesCache() {
Map<String, Set<String>> map = new HashMap<>(nameMap.size());
Set<String> tmpSet = new HashSet<>();
@@ -185,25 +182,10 @@ public class ClspGraph {
tmpSet.clear();
addSuperTypes(cls, tmpSet);
Set<String> result;
int size = tmpSet.size();
switch (size) {
case 0: {
result = Collections.emptySet();
break;
}
case 1: {
String supCls = tmpSet.iterator().next();
if (supCls.equals(Consts.CLASS_OBJECT)) {
result = OBJECT_SINGLE_SET;
} else {
result = Collections.singleton(supCls);
}
break;
}
default: {
result = new HashSet<>(tmpSet);
break;
}
if (tmpSet.isEmpty()) {
result = Collections.emptySet();
} else {
result = new HashSet<>(tmpSet);
}
map.put(cls.getName(), result);
}
@@ -26,7 +26,6 @@ import jadx.api.plugins.input.data.annotations.EncodedType;
import jadx.api.plugins.input.data.annotations.EncodedValue;
import jadx.api.plugins.input.data.attributes.JadxAttrType;
import jadx.core.Consts;
import jadx.core.codegen.utils.CodeGenUtils;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.FieldInitInsnAttr;
@@ -46,6 +45,7 @@ import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.CodeGenUtils;
import jadx.core.utils.EncodedValueUtils;
import jadx.core.utils.Utils;
import jadx.core.utils.android.AndroidResourcesUtils;
@@ -421,22 +421,18 @@ public class ClassGen {
if (f.contains(AFlag.DONT_GENERATE)) {
return;
}
if (f.contains(JadxAttrType.ANNOTATION_LIST)
|| f.contains(AType.JADX_COMMENTS)
|| f.contains(AType.CODE_COMMENTS)
|| f.getFieldInfo().hasAlias()) {
code.newLine();
}
if (Consts.DEBUG_USAGE) {
addFieldUsageInfo(code, f);
}
if (f.getFieldInfo().hasAlias()) {
CodeGenUtils.addRenamedComment(code, f, f.getName());
}
CodeGenUtils.addComments(code, f);
annotationGen.addForField(code, f);
code.startLine(f.getAccessFlags().makeString(f.checkCommentsLevel(CommentsLevel.INFO)));
boolean addInfoComments = f.checkCommentsLevel(CommentsLevel.INFO);
if (f.getFieldInfo().hasAlias() && addInfoComments) {
code.newLine();
CodeGenUtils.addRenamedComment(code, f, f.getName());
}
code.startLine(f.getAccessFlags().makeString(addInfoComments));
useType(code, f.getType());
code.add(' ');
code.attachDefinition(f);
@@ -15,7 +15,6 @@ import jadx.api.metadata.annotations.InsnCodeOffset;
import jadx.api.metadata.annotations.VarNode;
import jadx.api.plugins.input.data.MethodHandleType;
import jadx.api.plugins.input.data.annotations.EncodedValue;
import jadx.core.codegen.utils.CodeGenUtils;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.FieldInitInsnAttr;
@@ -61,6 +60,7 @@ import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.CodeGenUtils;
import jadx.core.utils.RegionUtils;
import jadx.core.utils.exceptions.CodegenException;
import jadx.core.utils.exceptions.JadxRuntimeException;
@@ -936,7 +936,7 @@ public class InsnGen {
makeInlinedLambdaMethod(code, customNode, callMth);
}
private void makeRefLambda(ICodeWriter code, InvokeCustomNode customNode) throws CodegenException {
private void makeRefLambda(ICodeWriter code, InvokeCustomNode customNode) {
InsnNode callInsn = customNode.getCallInsn();
if (callInsn instanceof ConstructorInsn) {
MethodInfo callMth = ((ConstructorInsn) callInsn).getCallMth();
@@ -950,7 +950,7 @@ public class InsnGen {
if (customNode.getHandleType() == MethodHandleType.INVOKE_STATIC) {
useClass(code, callMth.getDeclClass());
} else {
addArg(code, customNode.getArg(0));
code.add("this");
}
code.add("::").add(callMth.getAlias());
}
@@ -22,7 +22,6 @@ import jadx.api.plugins.input.data.attributes.JadxAttrType;
import jadx.api.plugins.input.data.attributes.types.AnnotationMethodParamsAttr;
import jadx.core.Consts;
import jadx.core.Jadx;
import jadx.core.codegen.utils.CodeGenUtils;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.JadxError;
@@ -44,6 +43,7 @@ import jadx.core.dex.trycatch.CatchAttr;
import jadx.core.dex.trycatch.ExceptionHandler;
import jadx.core.dex.visitors.DepthTraversal;
import jadx.core.dex.visitors.IDexTreeVisitor;
import jadx.core.utils.CodeGenUtils;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.CodegenException;
import jadx.core.utils.exceptions.JadxOverflowException;
@@ -5,7 +5,6 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -13,18 +12,14 @@ import jadx.api.CommentsLevel;
import jadx.api.ICodeWriter;
import jadx.api.metadata.annotations.InsnCodeOffset;
import jadx.api.metadata.annotations.VarNode;
import jadx.api.plugins.input.data.AccessFlags;
import jadx.api.plugins.input.data.annotations.EncodedValue;
import jadx.api.plugins.input.data.attributes.JadxAttrType;
import jadx.core.clsp.ClspClass;
import jadx.core.codegen.utils.CodeGenUtils;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.DeclareVariablesAttr;
import jadx.core.dex.attributes.nodes.ForceReturnAttr;
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.instructions.SwitchInsn;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.CodeVar;
@@ -49,6 +44,7 @@ import jadx.core.dex.regions.loops.LoopRegion;
import jadx.core.dex.regions.loops.LoopType;
import jadx.core.dex.trycatch.ExceptionHandler;
import jadx.core.utils.BlockUtils;
import jadx.core.utils.CodeGenUtils;
import jadx.core.utils.RegionUtils;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.CodegenException;
@@ -272,10 +268,19 @@ public class RegionGen extends InsnGen {
private void addCaseKey(ICodeWriter code, InsnArg arg, Object k) throws CodegenException {
if (k instanceof FieldNode) {
FieldNode fld = (FieldNode) k;
useField(code, fld.getFieldInfo(), fld);
} else if (k instanceof FieldInfo) {
useField(code, (FieldInfo) k, null);
FieldNode fn = (FieldNode) k;
if (fn.getParentClass().isEnum()) {
code.add(fn.getAlias());
} else {
staticField(code, fn.getFieldInfo());
if (mth.checkCommentsLevel(CommentsLevel.INFO)) {
// print original value, sometimes replaced with incorrect field
EncodedValue constVal = fn.get(JadxAttrType.CONSTANT_VALUE);
if (constVal != null && constVal.getValue() != null) {
code.add(" /* ").add(constVal.getValue().toString()).add(" */");
}
}
}
} else if (k instanceof Integer) {
code.add(TypeGen.literalToString((Integer) k, arg.getType(), mth, fallback));
} else {
@@ -283,28 +288,6 @@ public class RegionGen extends InsnGen {
}
}
private void useField(ICodeWriter code, FieldInfo fldInfo, @Nullable FieldNode fld) throws CodegenException {
boolean isEnum;
if (fld != null) {
isEnum = fld.getParentClass().isEnum();
} else {
ClspClass clsDetails = root.getClsp().getClsDetails(fldInfo.getDeclClass().getType());
isEnum = clsDetails != null && clsDetails.hasAccFlag(AccessFlags.ENUM);
}
if (isEnum) {
code.add(fldInfo.getAlias());
return;
}
staticField(code, fldInfo);
if (fld != null && mth.checkCommentsLevel(CommentsLevel.INFO)) {
// print original value, sometimes replaced with incorrect field
EncodedValue constVal = fld.get(JadxAttrType.CONSTANT_VALUE);
if (constVal != null && constVal.getValue() != null) {
code.add(" /* ").add(constVal.getValue().toString()).add(" */");
}
}
}
public void makeTryCatch(TryCatchRegion region, ICodeWriter code) throws CodegenException {
code.startLine("try {");
@@ -56,7 +56,7 @@ public class SimpleModeHelper {
if (!block.contains(AFlag.EXC_BOTTOM_SPLITTER)) {
startLabel.set(block.getId());
}
if (prev.getSuccessors().size() == 1 && !mth.isPreExitBlock(prev)) {
if (prev.getSuccessors().size() == 1 && !mth.isPreExitBlocks(prev)) {
endGoto.set(prev.getId());
}
}
@@ -68,7 +68,7 @@ public class SimpleModeHelper {
if (block.contains(AType.EXC_HANDLER)) {
startLabel.set(block.getId());
}
if (nextBlock == null && !mth.isPreExitBlock(block)) {
if (nextBlock == null && !mth.isPreExitBlocks(block)) {
endGoto.set(block.getId());
}
prev = block;
@@ -145,7 +145,7 @@ public class SimpleModeHelper {
// DFS sort blocks to reduce goto count
private List<BlockNode> getSortedBlocks() {
List<BlockNode> list = new ArrayList<>(mth.getBasicBlocks().size());
BlockUtils.visitDFS(mth, list::add);
BlockUtils.dfsVisit(mth, list::add);
return list;
}
}
@@ -24,7 +24,6 @@ import jadx.core.codegen.json.cls.JsonClass;
import jadx.core.codegen.json.cls.JsonCodeLine;
import jadx.core.codegen.json.cls.JsonField;
import jadx.core.codegen.json.cls.JsonMethod;
import jadx.core.codegen.utils.CodeGenUtils;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.instructions.args.ArgType;
@@ -32,6 +31,7 @@ import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.CodeGenUtils;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxRuntimeException;
@@ -86,7 +86,7 @@ public class JsonCodeGen {
jsonCls.setInterfaces(Utils.collectionMap(cls.getInterfaces(), clsType -> getTypeAlias(classGen, clsType)));
}
ICodeWriter cw = new SimpleCodeWriter(args);
ICodeWriter cw = new SimpleCodeWriter();
CodeGenUtils.addErrorsAndComments(cw, cls);
classGen.addClassDeclaration(cw);
jsonCls.setDeclaration(cw.getCodeStr());
@@ -130,7 +130,7 @@ public class JsonCodeGen {
jsonField.setAlias(field.getAlias());
}
ICodeWriter cw = new SimpleCodeWriter(args);
ICodeWriter cw = new SimpleCodeWriter();
classGen.addField(cw, field);
jsonField.setDeclaration(cw.getCodeStr());
jsonField.setAccessFlags(field.getAccessFlags().rawValue());
@@ -154,7 +154,7 @@ public class JsonCodeGen {
jsonMth.setArguments(Utils.collectionMap(mth.getMethodInfo().getArgumentsTypes(), clsType -> getTypeAlias(classGen, clsType)));
MethodGen mthGen = new MethodGen(classGen, mth);
ICodeWriter cw = new AnnotatedCodeWriter(args);
ICodeWriter cw = new AnnotatedCodeWriter();
mthGen.addDefinition(cw);
jsonMth.setDeclaration(cw.getCodeStr());
jsonMth.setAccessFlags(mth.getAccessFlags().rawValue());
@@ -181,7 +181,7 @@ public class JsonCodeGen {
return Collections.emptyList();
}
String[] lines = codeStr.split(args.getCodeNewLineStr());
String[] lines = codeStr.split(ICodeWriter.NL);
Map<Integer, Integer> lineMapping = code.getCodeMetadata().getLineMapping();
ICodeMetadata metadata = code.getCodeMetadata();
long mthCodeOffset = mth.getMethodCodeOffset() + 16;
@@ -189,7 +189,7 @@ public class JsonCodeGen {
int linesCount = lines.length;
List<JsonCodeLine> codeLines = new ArrayList<>(linesCount);
int lineStartPos = 0;
int newLineLen = args.getCodeNewLineStr().length();
int newLineLen = ICodeWriter.NL.length();
for (int i = 0; i < linesCount; i++) {
String codeLine = lines[i];
int line = i + 2;
@@ -208,7 +208,7 @@ public class JsonCodeGen {
}
private String getTypeAlias(ClassGen classGen, ArgType clsType) {
ICodeWriter code = new SimpleCodeWriter(args);
ICodeWriter code = new SimpleCodeWriter();
classGen.useType(code, clsType);
return code.getCodeStr();
}
@@ -1,32 +0,0 @@
package jadx.core.codegen.utils;
import jadx.api.data.CommentStyle;
import jadx.api.data.ICodeComment;
public class CodeComment {
private final String comment;
private final CommentStyle style;
public CodeComment(String comment, CommentStyle style) {
this.comment = comment;
this.style = style;
}
public CodeComment(ICodeComment comment) {
this(comment.getComment(), comment.getStyle());
}
public String getComment() {
return comment;
}
public CommentStyle getStyle() {
return style;
}
@Override
public String toString() {
return "CodeComment{" + style + ": '" + comment + "'}";
}
}
@@ -1,20 +0,0 @@
package jadx.core.deobf;
import jadx.api.JadxArgs;
import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.visitors.AbstractVisitor;
import jadx.core.utils.exceptions.JadxException;
public class InitRenameProviders extends AbstractVisitor {
@Override
public void init(RootNode root) throws JadxException {
JadxArgs args = root.getArgs();
if (args.isDeobfuscationOn() || !args.getRenameFlags().isEmpty()) {
args.getAliasProvider().init(root);
}
if (args.isDeobfuscationOn()) {
args.getRenameCondition().init(root);
}
}
}
@@ -5,81 +5,39 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.PackageNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.Utils;
public class DeobfWhitelist extends AbstractDeobfCondition {
private static final Logger LOG = LoggerFactory.getLogger(DeobfWhitelist.class);
public static final List<String> DEFAULT_LIST = Arrays.asList(
"android.support.*",
"android.os.*",
"android.support.v4.*",
"android.support.v7.*",
"android.support.v4.os.*",
"android.support.annotation.Px",
"androidx.core.os.*",
"androidx.annotation.*");
"androidx.annotation.Px");
public static final String DEFAULT_STR = Utils.listToString(DEFAULT_LIST, " ");
private final Set<String> packages = new HashSet<>();
private final Set<ClassNode> classes = new HashSet<>();
private boolean reportMissingItems = false;
private final Set<String> classes = new HashSet<>();
@Override
public void init(RootNode root) {
packages.clear();
classes.clear();
List<String> excludeList = root.getArgs().getDeobfuscationWhitelist();
reportMissingItems = !excludeList.equals(DEFAULT_LIST);
for (String name : excludeList) {
if (name.isEmpty()) {
continue;
}
if (name.endsWith(".*")) {
excludePackage(root, name.substring(0, name.length() - 2));
} else {
excludeClass(root, name);
for (String whitelistItem : root.getArgs().getDeobfuscationWhitelist()) {
if (!whitelistItem.isEmpty()) {
if (whitelistItem.endsWith(".*")) {
packages.add(whitelistItem.substring(0, whitelistItem.length() - 2));
} else {
classes.add(whitelistItem);
}
}
}
LOG.debug("Excluded from deobfuscation: {} packages, {} classes", packages.size(), classes.size());
}
private void excludeClass(RootNode root, String clsFullName) {
ClassNode cls = root.resolveClass(clsFullName);
if (cls == null) {
if (reportMissingItems) {
LOG.info("Can't exclude from deobfuscation: class '{}' not found", clsFullName);
}
return;
}
excludeClsNode(cls);
}
private void excludeClsNode(ClassNode cls) {
classes.add(cls);
cls.addInfoComment("Class excluded from deobfuscation");
}
private void excludePackage(RootNode root, String fullPkgName) {
PackageNode pkg = root.resolvePackage(fullPkgName);
if (pkg == null) {
if (reportMissingItems) {
LOG.info("Can't exclude from deobfuscation: package '{}' not found", fullPkgName);
}
return;
}
excludePkgNode(pkg);
}
private void excludePkgNode(PackageNode pkg) {
packages.add(pkg.getFullName());
pkg.getClasses().forEach(this::excludeClsNode);
pkg.getSubPackages().forEach(this::excludePkgNode);
}
@Override
@@ -92,19 +50,9 @@ public class DeobfWhitelist extends AbstractDeobfCondition {
@Override
public Action check(ClassNode cls) {
if (classes.contains(cls)) {
if (classes.contains(cls.getClassInfo().getFullName())) {
return Action.FORBID_RENAME;
}
return Action.NO_ACTION;
}
@Override
public Action check(FieldNode fld) {
return check(fld.getParentClass());
}
@Override
public Action check(MethodNode mth) {
return check(mth.getParentClass());
}
}
@@ -106,5 +106,4 @@ public enum AFlag {
DONT_UNLOAD_CLASS, // don't unload class after code generation (only for tests and debug!)
RESOLVE_JAVA_JSR,
COMPUTE_POST_DOM,
}
@@ -2,7 +2,6 @@ package jadx.core.dex.attributes;
import jadx.api.plugins.input.data.attributes.IJadxAttrType;
import jadx.api.plugins.input.data.attributes.IJadxAttribute;
import jadx.core.codegen.utils.CodeComment;
import jadx.core.dex.attributes.nodes.AnonymousClassAttr;
import jadx.core.dex.attributes.nodes.ClassTypeVarsAttr;
import jadx.core.dex.attributes.nodes.DeclareVariablesAttr;
@@ -45,7 +44,7 @@ import jadx.core.dex.trycatch.TryCatchBlockAttr;
public final class AType<T extends IJadxAttribute> implements IJadxAttrType<T> {
// class, method, field, insn
public static final AType<AttrList<CodeComment>> CODE_COMMENTS = new AType<>();
public static final AType<AttrList<String>> CODE_COMMENTS = new AType<>();
// class, method, field
public static final AType<RenameReasonAttr> RENAME_REASON = new AType<>();
@@ -139,12 +139,14 @@ public abstract class AttrNode implements IAttributeNode {
storage = EMPTY_ATTR_STORAGE;
}
/**
* Remove all attribute
*/
public void unloadAttributes() {
if (storage == EMPTY_ATTR_STORAGE) {
return;
}
storage.unloadAttributes();
storage.clearFlags();
unloadIfEmpty();
}
@@ -102,10 +102,6 @@ public class AttributeStorage {
flags.remove(flag);
}
public void clearFlags() {
flags.clear();
}
public <T extends IJadxAttribute> void remove(IJadxAttrType<T> type) {
if (!attributes.isEmpty()) {
writeAttributes(map -> map.remove(type));
@@ -4,6 +4,7 @@ import java.util.Objects;
import org.jetbrains.annotations.NotNull;
import jadx.api.ICodeWriter;
import jadx.core.utils.Utils;
public class JadxError implements Comparable<JadxError> {
@@ -54,7 +55,7 @@ public class JadxError implements Comparable<JadxError> {
str.append(cause.getClass());
str.append(':');
str.append(cause.getMessage());
str.append('\n');
str.append(ICodeWriter.NL);
str.append(Utils.getStackTrace(cause));
}
return str.toString();
@@ -2,6 +2,7 @@ package jadx.core.dex.attributes.nodes;
import java.util.List;
import jadx.api.ICodeWriter;
import jadx.api.plugins.input.data.ILocalVar;
import jadx.api.plugins.input.data.attributes.IJadxAttribute;
import jadx.core.dex.attributes.AType;
@@ -25,6 +26,6 @@ public class LocalVarsDebugInfoAttr implements IJadxAttribute {
@Override
public String toString() {
return "Debug Info:\n " + Utils.listToString(localVars, "\n ");
return "Debug Info:" + ICodeWriter.NL + " " + Utils.listToString(localVars, ICodeWriter.NL + " ");
}
}
@@ -1,8 +1,7 @@
package jadx.core.dex.attributes.nodes;
import jadx.api.CommentsLevel;
import jadx.api.data.CommentStyle;
import jadx.core.codegen.utils.CodeComment;
import jadx.api.ICodeWriter;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.nodes.ICodeNode;
@@ -26,11 +25,7 @@ public abstract class NotificationAttrNode extends LineAttrNode implements ICode
}
public void addCodeComment(String comment) {
addAttr(AType.CODE_COMMENTS, new CodeComment(comment, CommentStyle.LINE));
}
public void addCodeComment(String comment, CommentStyle style) {
addAttr(AType.CODE_COMMENTS, new CodeComment(comment, style));
addAttr(AType.CODE_COMMENTS, comment);
}
public void addWarnComment(String warn) {
@@ -38,7 +33,7 @@ public abstract class NotificationAttrNode extends LineAttrNode implements ICode
}
public void addWarnComment(String warn, Throwable exc) {
String commentStr = warn + root().getArgs().getCodeNewLineStr() + Utils.getStackTrace(exc);
String commentStr = warn + ICodeWriter.NL + Utils.getStackTrace(exc);
JadxCommentsAttr.add(this, CommentsLevel.WARN, commentStr);
}
@@ -3,6 +3,7 @@ package jadx.core.dex.attributes.nodes;
import java.util.ArrayList;
import java.util.List;
import jadx.api.ICodeWriter;
import jadx.api.plugins.input.data.attributes.IJadxAttribute;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.instructions.PhiInsn;
@@ -32,7 +33,7 @@ public class PhiListAttr implements IJadxAttribute {
}
}
for (PhiInsn phiInsn : list) {
sb.append('\n').append(" ").append(phiInsn);
sb.append(ICodeWriter.NL).append(" ").append(phiInsn);
}
return sb.toString();
}
@@ -2,44 +2,47 @@ package jadx.core.dex.info;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.jetbrains.annotations.Nullable;
import jadx.api.JadxArgs;
import jadx.api.plugins.input.data.annotations.EncodedValue;
import jadx.api.plugins.input.data.attributes.JadxAttrType;
import jadx.core.dex.instructions.args.LiteralArg;
import jadx.core.dex.instructions.args.PrimitiveType;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.IFieldInfoRef;
import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.visitors.prepare.CollectConstValues;
public class ConstStorage {
private static final class ValueStorage {
private final Map<Object, IFieldInfoRef> values = new ConcurrentHashMap<>();
private final Map<Object, FieldNode> values = new ConcurrentHashMap<>();
private final Set<Object> duplicates = new HashSet<>();
public Map<Object, IFieldInfoRef> getValues() {
public Map<Object, FieldNode> getValues() {
return values;
}
public IFieldInfoRef get(Object key) {
public FieldNode get(Object key) {
return values.get(key);
}
/**
* @return true if this value is duplicated
*/
public boolean put(Object value, IFieldInfoRef fld) {
public boolean put(Object value, FieldNode fld) {
if (duplicates.contains(value)) {
values.remove(value);
return true;
}
IFieldInfoRef prev = values.put(value, fld);
FieldNode prev = values.put(value, fld);
if (prev != null) {
values.remove(value);
duplicates.add(value);
@@ -53,13 +56,14 @@ public class ConstStorage {
}
void removeForCls(ClassNode cls) {
values.entrySet().removeIf(entry -> {
IFieldInfoRef field = entry.getValue();
if (field instanceof FieldNode) {
return ((FieldNode) field).getParentClass().equals(cls);
Iterator<Entry<Object, FieldNode>> it = values.entrySet().iterator();
while (it.hasNext()) {
Entry<Object, FieldNode> entry = it.next();
FieldNode field = entry.getValue();
if (field.getParentClass().equals(cls)) {
it.remove();
}
return false;
});
}
}
}
@@ -73,24 +77,27 @@ public class ConstStorage {
this.replaceEnabled = args.isReplaceConsts();
}
public void addConstField(FieldNode fld, Object value, boolean isPublic) {
if (isPublic) {
addGlobalConstField(fld, value);
} else {
getClsValues(fld.getParentClass()).put(value, fld);
public void processConstFields(ClassNode cls, List<FieldNode> staticFields) {
if (!replaceEnabled || staticFields.isEmpty()) {
return;
}
for (FieldNode f : staticFields) {
Object value = getFieldConstValue(f);
if (value != null) {
addConstField(cls, f, value, f.getAccessFlags().isPublic());
}
}
}
public void addGlobalConstField(IFieldInfoRef fld, Object value) {
globalValues.put(value, fld);
}
/**
* Use method from CollectConstValues class
*/
@Deprecated
public static @Nullable Object getFieldConstValue(FieldNode fld) {
return CollectConstValues.getFieldConstValue(fld);
AccessInfo accFlags = fld.getAccessFlags();
if (accFlags.isStatic() && accFlags.isFinal()) {
EncodedValue constVal = fld.get(JadxAttrType.CONSTANT_VALUE);
if (constVal != null) {
return constVal.getValue();
}
}
return null;
}
public void removeForClass(ClassNode cls) {
@@ -98,11 +105,20 @@ public class ConstStorage {
globalValues.removeForCls(cls);
}
private void addConstField(ClassNode cls, FieldNode fld, Object value, boolean isPublic) {
if (isPublic) {
globalValues.put(value, fld);
} else {
getClsValues(cls).put(value, fld);
}
}
private ValueStorage getClsValues(ClassNode cls) {
return classes.computeIfAbsent(cls, c -> new ValueStorage());
}
public @Nullable IFieldInfoRef getConstField(ClassNode cls, Object value, boolean searchGlobal) {
@Nullable
public FieldNode getConstField(ClassNode cls, Object value, boolean searchGlobal) {
if (!replaceEnabled) {
return null;
}
@@ -121,7 +137,7 @@ public class ConstStorage {
while (current != null) {
ValueStorage classValues = classes.get(current);
if (classValues != null) {
IFieldInfoRef field = classValues.get(value);
FieldNode field = classValues.get(value);
if (field != null) {
if (foundInGlobal) {
return null;
@@ -166,7 +182,8 @@ public class ConstStorage {
return null;
}
public @Nullable IFieldInfoRef getConstFieldByLiteralArg(ClassNode cls, LiteralArg arg) {
@Nullable
public FieldNode getConstFieldByLiteralArg(ClassNode cls, LiteralArg arg) {
if (!replaceEnabled) {
return null;
}
@@ -208,7 +225,7 @@ public class ConstStorage {
return resourcesNames;
}
public Map<Object, IFieldInfoRef> getGlobalConstFields() {
public Map<Object, FieldNode> getGlobalConstFields() {
return globalValues.getValues();
}
@@ -5,10 +5,9 @@ import java.util.Objects;
import jadx.api.plugins.input.data.IFieldRef;
import jadx.core.codegen.TypeGen;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.IFieldInfoRef;
import jadx.core.dex.nodes.RootNode;
public final class FieldInfo implements IFieldInfoRef {
public final class FieldInfo {
private final ClassInfo declClass;
private final String name;
@@ -77,11 +76,6 @@ public final class FieldInfo implements IFieldInfoRef {
return name.equals(other.name) && type.equals(other.type);
}
@Override
public FieldInfo getFieldInfo() {
return this;
}
@Override
public boolean equals(Object o) {
if (this == o) {
@@ -2,7 +2,6 @@ package jadx.core.dex.instructions;
import java.util.Objects;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.utils.InsnUtils;
import jadx.core.utils.Utils;
@@ -24,10 +23,6 @@ public class IndexInsnNode extends InsnNode {
this.index = index;
}
public ArgType getIndexAsType() {
return (ArgType) index;
}
@Override
public IndexInsnNode copy() {
return copyCommonParams(new IndexInsnNode(insnType, index, getArgsCount()));
@@ -521,7 +521,6 @@ public class InsnDecoder {
if (payload != null) {
swInsn.attachSwitchData(new SwitchData((ISwitchPayload) payload), insn.getTarget());
}
method.add(AFlag.COMPUTE_POST_DOM);
return swInsn;
}
@@ -103,6 +103,6 @@ public class InvokeNode extends BaseInvokeNode {
@Override
public String toString() {
return baseString() + " " + type + " call: " + mth + attributesString();
return baseString() + " type: " + type + " call: " + mth + attributesString();
}
}
@@ -5,6 +5,7 @@ import java.util.List;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import jadx.api.ICodeWriter;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.InsnNode;
@@ -110,20 +111,20 @@ public class SwitchInsn extends TargetInsnNode {
int[] keys = switchData.getKeys();
if (targetBlocks != null) {
for (int i = 0; i < size; i++) {
sb.append('\n');
sb.append(ICodeWriter.NL);
sb.append(" case ").append(keys[i]).append(": goto ").append(targetBlocks[i]);
}
if (def != -1) {
sb.append('\n').append(" default: goto ").append(defTargetBlock);
sb.append(ICodeWriter.NL).append(" default: goto ").append(defTargetBlock);
}
} else {
int[] targets = switchData.getTargets();
for (int i = 0; i < size; i++) {
sb.append('\n');
sb.append(ICodeWriter.NL);
sb.append(" case ").append(keys[i]).append(": goto ").append(InsnUtils.formatOffset(targets[i]));
}
if (def != -1) {
sb.append('\n');
sb.append(ICodeWriter.NL);
sb.append(" default: goto ").append(InsnUtils.formatOffset(def));
}
}
@@ -16,8 +16,8 @@ import jadx.core.utils.InsnRemover;
import jadx.core.utils.exceptions.JadxRuntimeException;
/**
* Instruction argument.
* Can be: register, literal, instruction or name
* Instruction argument,
* argument can be register, literal or instruction
*/
public abstract class InsnArg extends Typed {
@@ -132,10 +132,6 @@ public abstract class InsnArg extends Typed {
}
InsnArg arg = wrapInsnIntoArg(insn);
InsnArg oldArg = parent.getArg(i);
if (arg.getType() == ArgType.UNKNOWN) {
// restore arg type if wrapped insn missing result
arg.setType(oldArg.getType());
}
parent.setArg(i, arg);
InsnRemover.unbindArgUsage(mth, oldArg);
if (unbind) {
@@ -213,20 +209,7 @@ public abstract class InsnArg extends Typed {
}
public boolean isZeroLiteral() {
return false;
}
public boolean isZeroConst() {
if (isZeroLiteral()) {
return true;
}
if (isInsnWrap()) {
InsnNode wrapInsn = ((InsnWrapArg) this).getWrapInsn();
if (wrapInsn.getType() == InsnType.CONST) {
return wrapInsn.getArg(0).isZeroLiteral();
}
}
return false;
return isLiteral() && (((LiteralArg) this)).getLiteral() == 0;
}
public boolean isFalse() {
@@ -282,9 +265,6 @@ public abstract class InsnArg extends Typed {
}
public boolean isSameVar(RegisterArg arg) {
if (arg == null) {
return false;
}
if (isRegister()) {
return ((RegisterArg) this).sameRegAndSVar(arg);
}
@@ -300,8 +280,4 @@ public abstract class InsnArg extends Typed {
public InsnArg duplicate() {
return this;
}
public String toShortString() {
return this.toString();
}
}
@@ -1,5 +1,7 @@
package jadx.core.dex.instructions.args;
import java.util.Objects;
import org.jetbrains.annotations.NotNull;
import jadx.core.dex.instructions.ConstStringNode;
@@ -72,19 +74,11 @@ public final class InsnWrapArg extends InsnArg {
return true;
}
@Override
public String toShortString() {
if (wrappedInsn.getType() == InsnType.CONST_STR) {
return "(\"" + ((ConstStringNode) wrappedInsn).getString() + "\")";
}
return "(wrap:" + type + ":" + wrappedInsn.getType() + ')';
}
@Override
public String toString() {
if (wrappedInsn.getType() == InsnType.CONST_STR) {
if (wrappedInsn.getType() == InsnType.CONST_STR && Objects.equals(type, ArgType.STRING)) {
return "(\"" + ((ConstStringNode) wrappedInsn).getString() + "\")";
}
return "(wrap:" + type + ":" + wrappedInsn + ')';
return "(wrap: " + type + " : " + wrappedInsn + ')';
}
}
@@ -58,11 +58,6 @@ public final class LiteralArg extends InsnArg {
return true;
}
@Override
public boolean isZeroLiteral() {
return literal == 0;
}
public boolean isInteger() {
switch (type.getPrimitiveType()) {
case INT:
@@ -130,11 +125,6 @@ public final class LiteralArg extends InsnArg {
return literal == that.literal && getType().equals(that.getType());
}
@Override
public String toShortString() {
return Long.toString(literal);
}
@Override
public String toString() {
try {
@@ -48,11 +48,6 @@ public final class NamedArg extends InsnArg implements Named {
return name.equals(((NamedArg) o).name);
}
@Override
public String toShortString() {
return name;
}
@Override
public String toString() {
return '(' + name + ' ' + type + ')';
@@ -215,16 +215,6 @@ public class RegisterArg extends InsnArg implements Named {
&& Objects.equals(sVar, other.getSVar());
}
@Override
public String toShortString() {
StringBuilder sb = new StringBuilder();
sb.append("r").append(regNum);
if (sVar != null) {
sb.append('v').append(sVar.getVersion());
}
return sb.toString();
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
@@ -2,7 +2,6 @@ package jadx.core.dex.instructions.args;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -24,12 +23,9 @@ import jadx.core.dex.visitors.typeinference.TypeInfo;
import jadx.core.utils.StringUtils;
import jadx.core.utils.exceptions.JadxRuntimeException;
public class SSAVar implements Comparable<SSAVar> {
public class SSAVar {
private static final Logger LOG = LoggerFactory.getLogger(SSAVar.class);
private static final Comparator<SSAVar> SSA_VAR_COMPARATOR =
Comparator.comparingInt(SSAVar::getRegNum).thenComparingInt(SSAVar::getVersion);
private final int regNum;
private final int version;
@@ -260,6 +256,34 @@ public class SSAVar implements Comparable<SSAVar> {
return codeVar != null;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof SSAVar)) {
return false;
}
SSAVar ssaVar = (SSAVar) o;
return regNum == ssaVar.regNum && version == ssaVar.version;
}
@Override
public int hashCode() {
return 31 * regNum + version;
}
public String toShortString() {
return "r" + regNum + 'v' + version;
}
@Override
public String toString() {
return toShortString()
+ (StringUtils.notEmpty(getName()) ? " '" + getName() + "' " : "")
+ ' ' + typeInfo.getType();
}
public String getDetailedVarInfo(MethodNode mth) {
Set<ArgType> types = new HashSet<>();
Set<String> names = Collections.emptySet();
@@ -299,37 +323,4 @@ public class SSAVar implements Comparable<SSAVar> {
}
return sb.toString();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof SSAVar)) {
return false;
}
SSAVar ssaVar = (SSAVar) o;
return regNum == ssaVar.regNum && version == ssaVar.version;
}
@Override
public int hashCode() {
return 31 * regNum + version;
}
@Override
public int compareTo(@NotNull SSAVar o) {
return SSA_VAR_COMPARATOR.compare(this, o);
}
public String toShortString() {
return "r" + regNum + 'v' + version;
}
@Override
public String toString() {
return toShortString()
+ (StringUtils.notEmpty(getName()) ? " '" + getName() + "' " : "")
+ ' ' + typeInfo.getType();
}
}
@@ -46,11 +46,6 @@ public final class BlockNode extends AttrNode implements IBlock, Comparable<Bloc
*/
private BitSet doms = EmptyBitSet.EMPTY;
/**
* Post dominators, excluding self
*/
private BitSet postDoms = EmptyBitSet.EMPTY;
/**
* Dominance frontier
*/
@@ -61,11 +56,6 @@ public final class BlockNode extends AttrNode implements IBlock, Comparable<Bloc
*/
private BlockNode idom;
/**
* Immediate post dominator
*/
private BlockNode iPostDom;
/**
* Blocks on which dominates this block
*/
@@ -175,14 +165,6 @@ public final class BlockNode extends AttrNode implements IBlock, Comparable<Bloc
this.doms = doms;
}
public BitSet getPostDoms() {
return postDoms;
}
public void setPostDoms(BitSet postDoms) {
this.postDoms = postDoms;
}
public BitSet getDomFrontier() {
return domFrontier;
}
@@ -202,14 +184,6 @@ public final class BlockNode extends AttrNode implements IBlock, Comparable<Bloc
this.idom = idom;
}
public BlockNode getIPostDom() {
return iPostDom;
}
public void setIPostDom(BlockNode iPostDom) {
this.iPostDom = iPostDom;
}
public List<BlockNode> getDominatesOn() {
return dominatesOn;
}
@@ -259,6 +233,6 @@ public final class BlockNode extends AttrNode implements IBlock, Comparable<Bloc
@Override
public String toString() {
return "B:" + id + ':' + InsnUtils.formatOffset(startOffset);
return "B:" + cid + ':' + InsnUtils.formatOffset(startOffset);
}
}
@@ -9,6 +9,7 @@ import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -18,12 +19,10 @@ import org.slf4j.LoggerFactory;
import jadx.api.DecompilationMode;
import jadx.api.ICodeCache;
import jadx.api.ICodeInfo;
import jadx.api.ICodeWriter;
import jadx.api.JadxArgs;
import jadx.api.JavaClass;
import jadx.api.impl.SimpleCodeInfo;
import jadx.api.impl.SimpleCodeWriter;
import jadx.api.metadata.ICodeAnnotation;
import jadx.api.metadata.annotations.NodeDeclareRef;
import jadx.api.plugins.input.data.IClassData;
import jadx.api.plugins.input.data.IFieldData;
import jadx.api.plugins.input.data.IMethodData;
@@ -247,15 +246,19 @@ public class ClassNode extends NotificationAttrNode
if (fields.isEmpty()) {
return;
}
// bytecode can omit field initialization to 0 (of any type)
// add explicit init to all static final fields
// incorrect initializations will be removed if assign found in class init
for (FieldNode fld : fields) {
AccessInfo accFlags = fld.getAccessFlags();
if (accFlags.isStatic() && accFlags.isFinal() && fld.get(JadxAttrType.CONSTANT_VALUE) == null) {
fld.addAttr(EncodedValue.NULL);
List<FieldNode> staticFields = fields.stream().filter(FieldNode::isStatic).collect(Collectors.toList());
for (FieldNode f : staticFields) {
if (f.getAccessFlags().isFinal() && f.get(JadxAttrType.CONSTANT_VALUE) == null) {
// incorrect initialization will be removed if assign found in constructor
f.addAttr(EncodedValue.NULL);
}
}
try {
// process const fields
root().getConstValues().processConstFields(this, staticFields);
} catch (Exception e) {
this.addWarnComment("Failed to load initial values for static fields", e);
}
}
private boolean checkSourceFilenameAttr() {
@@ -309,8 +312,6 @@ public class ClassNode extends NotificationAttrNode
return decompile(true);
}
private static final Object DECOMPILE_WITH_MODE_SYNC = new Object();
/**
* WARNING: Slow operation! Use with caution!
*/
@@ -319,18 +320,15 @@ public class ClassNode extends NotificationAttrNode
if (mode == baseMode) {
return decompile(true);
}
synchronized (DECOMPILE_WITH_MODE_SYNC) {
JadxArgs args = root.getArgs();
try {
unload();
args.setDecompilationMode(mode);
ProcessClass process = new ProcessClass(args);
process.initPasses(root);
return process.generateCode(this);
} finally {
args.setDecompilationMode(baseMode);
unload();
}
JadxArgs args = root.getArgs();
try {
unload();
args.setDecompilationMode(mode);
ProcessClass process = new ProcessClass(args);
process.initPasses(root);
return process.generateCode(this);
} finally {
args.setDecompilationMode(baseMode);
}
}
@@ -385,44 +383,20 @@ public class ClassNode extends NotificationAttrNode
return code;
}
}
ICodeInfo codeInfo = generateClassCode();
if (codeInfo != ICodeInfo.EMPTY) {
codeCache.add(clsRawName, codeInfo);
}
return codeInfo;
}
private ICodeInfo generateClassCode() {
ICodeInfo codeInfo;
try {
if (Consts.DEBUG) {
LOG.debug("Decompiling class: {}", this);
}
ICodeInfo codeInfo = root.getProcessClasses().generateCode(this);
processDefinitionAnnotations(codeInfo);
return codeInfo;
codeInfo = root.getProcessClasses().generateCode(this);
} catch (Throwable e) {
addError("Code generation failed", e);
return new SimpleCodeInfo(Utils.getStackTrace(e));
codeInfo = new SimpleCodeInfo(Utils.getStackTrace(e));
}
}
/**
* Save node definition positions found in code
*/
private static void processDefinitionAnnotations(ICodeInfo codeInfo) {
Map<Integer, ICodeAnnotation> annotations = codeInfo.getCodeMetadata().getAsMap();
if (annotations.isEmpty()) {
return;
}
for (Map.Entry<Integer, ICodeAnnotation> entry : annotations.entrySet()) {
ICodeAnnotation ann = entry.getValue();
if (ann.getAnnType() == AnnType.DECLARATION) {
NodeDeclareRef declareRef = (NodeDeclareRef) ann;
int pos = entry.getKey();
declareRef.setDefPos(pos);
declareRef.getNode().setDefPosition(pos);
}
if (codeInfo != ICodeInfo.EMPTY) {
codeCache.add(clsRawName, codeInfo);
}
return codeInfo;
}
@Nullable
@@ -458,7 +432,7 @@ public class ClassNode extends NotificationAttrNode
}
methods.forEach(MethodNode::unload);
innerClasses.forEach(ClassNode::unload);
fields.forEach(FieldNode::unload);
fields.forEach(FieldNode::unloadAttributes);
unloadAttributes();
setState(NOT_LOADED);
this.loadStage = LoadStage.NONE;
@@ -508,15 +482,17 @@ public class ClassNode extends NotificationAttrNode
fields.add(fld);
}
public @Nullable IFieldInfoRef getConstField(Object obj) {
public FieldNode getConstField(Object obj) {
return getConstField(obj, true);
}
public @Nullable IFieldInfoRef getConstField(Object obj, boolean searchGlobal) {
@Nullable
public FieldNode getConstField(Object obj, boolean searchGlobal) {
return root().getConstValues().getConstField(this, obj, searchGlobal);
}
public @Nullable IFieldInfoRef getConstFieldByLiteralArg(LiteralArg arg) {
@Nullable
public FieldNode getConstFieldByLiteralArg(LiteralArg arg) {
return root().getConstValues().getConstFieldByLiteralArg(this, arg);
}
@@ -831,29 +807,33 @@ public class ClassNode extends NotificationAttrNode
public String getDisassembledCode() {
if (smali == null) {
SimpleCodeWriter code = new SimpleCodeWriter(root.getArgs());
getDisassembledCode(code);
StringBuilder sb = new StringBuilder();
getDisassembledCode(sb);
sb.append(ICodeWriter.NL);
Set<ClassNode> allInlinedClasses = new LinkedHashSet<>();
getInnerAndInlinedClassesRecursive(allInlinedClasses);
for (ClassNode innerClass : allInlinedClasses) {
innerClass.getDisassembledCode(code);
innerClass.getDisassembledCode(sb);
sb.append(ICodeWriter.NL);
}
smali = code.finish().getCodeStr();
smali = sb.toString();
}
return smali;
}
protected void getDisassembledCode(SimpleCodeWriter code) {
protected void getDisassembledCode(StringBuilder sb) {
if (clsData == null) {
code.startLine(String.format("###### Class %s is created by jadx", getFullName()));
sb.append(String.format("###### Class %s is created by jadx", getFullName()));
return;
}
code.startLine(String.format("###### Class %s (%s)", getFullName(), getRawName()));
sb.append(String.format("###### Class %s (%s)", getFullName(), getRawName()));
sb.append(ICodeWriter.NL);
try {
code.startLine(clsData.getDisassembledCode());
sb.append(clsData.getDisassembledCode());
} catch (Throwable e) {
code.startLine("Failed to disassemble class:");
code.startLine(Utils.getStackTrace(e));
sb.append("Failed to disassemble class:");
sb.append(ICodeWriter.NL);
sb.append(Utils.getStackTrace(e));
}
}
@@ -12,7 +12,7 @@ import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.utils.ListUtils;
public class FieldNode extends NotificationAttrNode implements ICodeNode, IFieldInfoRef {
public class FieldNode extends NotificationAttrNode implements ICodeNode {
private final ClassNode parentClass;
private final FieldInfo fieldInfo;
@@ -38,15 +38,10 @@ public class FieldNode extends NotificationAttrNode implements ICodeNode, IField
this.accFlags = new AccessInfo(accessFlags, AFType.FIELD);
}
public void unload() {
unloadAttributes();
}
public void updateType(ArgType type) {
this.type = type;
}
@Override
public FieldInfo getFieldInfo() {
return fieldInfo;
}
@@ -1,10 +0,0 @@
package jadx.core.dex.nodes;
import jadx.core.dex.info.FieldInfo;
/**
* Common interface for FieldInfo and FieldNode
*/
public interface IFieldInfoRef {
FieldInfo getFieldInfo();
}
@@ -10,6 +10,7 @@ import java.util.function.Function;
import org.jetbrains.annotations.Nullable;
import jadx.api.ICodeWriter;
import jadx.api.plugins.input.insns.InsnData;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
@@ -562,9 +563,9 @@ public class InsnNode extends LineAttrNode {
return false;
}
// wrap args
String separator = "\n ";
String separator = ICodeWriter.NL + " ";
sb.append(separator).append(Utils.listToString(arguments, separator));
sb.append('\n');
sb.append(ICodeWriter.NL);
return true;
}
@@ -253,7 +253,7 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
return mthInfo.getReturnType().equals(ArgType.VOID);
}
public List<VarNode> collectArgNodes() {
public List<VarNode> collectArgsWithoutLoading() {
ICodeInfo codeInfo = getTopParentClass().getCode();
int mthDefPos = getDefPosition();
int lineEndPos = CodeUtils.getLineEndForPos(codeInfo.getCodeStr(), mthDefPos);
@@ -360,13 +360,10 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
public void setBasicBlocks(List<BlockNode> blocks) {
this.blocks = blocks;
updateBlockIds(blocks);
}
public void updateBlockIds(List<BlockNode> blocks) {
int count = blocks.size();
for (int i = 0; i < count; i++) {
blocks.get(i).setId(i);
int i = 0;
for (BlockNode block : blocks) {
block.setId(i);
i++;
}
}
@@ -394,7 +391,7 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
return exitBlock.getPredecessors();
}
public boolean isPreExitBlock(BlockNode block) {
public boolean isPreExitBlocks(BlockNode block) {
List<BlockNode> successors = block.getSuccessors();
if (successors.size() == 1) {
return successors.get(0).equals(exitBlock);
@@ -54,8 +54,9 @@ import jadx.core.utils.StringUtils;
import jadx.core.utils.Utils;
import jadx.core.utils.android.AndroidResourcesUtils;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.xmlgen.IResTableParser;
import jadx.core.xmlgen.IResParser;
import jadx.core.xmlgen.ManifestAttributes;
import jadx.core.xmlgen.ResDecoder;
import jadx.core.xmlgen.ResourceStorage;
import jadx.core.xmlgen.entry.ResourceEntry;
import jadx.core.xmlgen.entry.ValuesParser;
@@ -92,6 +93,7 @@ public class RootNode {
private String appPackage;
@Nullable
private ClassNode appResClass;
private boolean isProto;
/**
* Optional decompiler reference
@@ -107,6 +109,16 @@ public class RootNode {
this.typeUpdate = new TypeUpdate(this);
this.methodUtils = new MethodUtils(this);
this.typeUtils = new TypeUtils(this);
this.isProto = args.getInputFiles().size() > 0 && args.getInputFiles().get(0).getName().toLowerCase().endsWith(".aab");
}
public void init() {
if (args.isDeobfuscationOn() || !args.getRenameFlags().isEmpty()) {
args.getAliasProvider().init(this);
}
if (args.isDeobfuscationOn()) {
args.getRenameCondition().init(this);
}
}
public void loadClasses(List<ICodeLoader> loadedInputs) {
@@ -191,25 +203,25 @@ public class RootNode {
rawClsMap.put(clsNode.getRawName(), clsNode);
}
public void loadResources(ResourcesLoader resLoader, List<ResourceFile> resources) {
public void loadResources(List<ResourceFile> resources) {
ResourceFile arsc = getResourceFile(resources);
if (arsc == null) {
LOG.debug("'resources.arsc' or 'resources.pb' file not found");
LOG.debug("'.arsc' file not found");
return;
}
try {
IResTableParser parser = ResourcesLoader.decodeStream(arsc, (size, is) -> resLoader.decodeTable(arsc, is));
IResParser parser = ResourcesLoader.decodeStream(arsc, (size, is) -> ResDecoder.decode(this, arsc, is));
if (parser != null) {
processResources(parser.getResStorage());
updateObfuscatedFiles(parser, resources);
updateManifestAttribMap(parser);
}
} catch (Exception e) {
LOG.error("Failed to parse 'resources.pb'/'.arsc' file", e);
LOG.error("Failed to parse '.arsc' file", e);
}
}
private void updateManifestAttribMap(IResTableParser parser) {
private void updateManifestAttribMap(IResParser parser) {
ManifestAttributes manifestAttributes = ManifestAttributes.getInstance();
manifestAttributes.updateAttributes(parser);
}
@@ -245,7 +257,7 @@ public class RootNode {
}
}
private void updateObfuscatedFiles(IResTableParser parser, List<ResourceFile> resources) {
private void updateObfuscatedFiles(IResParser parser, List<ResourceFile> resources) {
if (args.isSkipResources()) {
return;
}
@@ -703,6 +715,10 @@ public class RootNode {
return attributes;
}
public boolean isProto() {
return isProto;
}
public GradleInfoStorage getGradleInfoStorage() {
return gradleInfoStorage;
}
@@ -15,12 +15,7 @@ import jadx.core.utils.exceptions.CodegenException;
public final class SwitchRegion extends AbstractRegion implements IBranchRegion {
public static final Object DEFAULT_CASE_KEY = new Object() {
@Override
public String toString() {
return "default";
}
};
public static final Object DEFAULT_CASE_KEY = new Object();
private final BlockNode header;
@@ -96,7 +91,7 @@ public final class SwitchRegion extends AbstractRegion implements IBranchRegion
for (CaseInfo caseInfo : cases) {
List<String> keyStrings = Utils.collectionMap(caseInfo.getKeys(),
k -> k == DEFAULT_CASE_KEY ? "default" : k.toString());
sb.append("\n case ")
sb.append(ICodeWriter.NL).append(" case ")
.append(Utils.listToString(keyStrings))
.append(" -> ").append(caseInfo.getContainer());
}
@@ -14,7 +14,6 @@ import jadx.api.data.ICodeComment;
import jadx.api.data.ICodeData;
import jadx.api.data.IJavaCodeRef;
import jadx.api.data.IJavaNodeRef;
import jadx.core.codegen.utils.CodeComment;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttributeNode;
import jadx.core.dex.nodes.ClassNode;
@@ -59,7 +58,7 @@ public class AttachCommentsVisitor extends AbstractVisitor {
IJavaNodeRef nodeRef = comment.getNodeRef();
switch (nodeRef.getType()) {
case CLASS:
addComment(cls, comment);
addComment(cls, comment.getComment());
break;
case FIELD:
@@ -67,7 +66,7 @@ public class AttachCommentsVisitor extends AbstractVisitor {
if (fieldNode == null) {
LOG.warn("Field reference not found: {}", nodeRef);
} else {
addComment(fieldNode, comment);
addComment(fieldNode, comment.getComment());
}
break;
@@ -78,7 +77,7 @@ public class AttachCommentsVisitor extends AbstractVisitor {
} else {
IJavaCodeRef codeRef = comment.getCodeRef();
if (codeRef == null) {
addComment(methodNode, comment);
addComment(methodNode, comment.getComment());
} else {
processCustomAttach(methodNode, codeRef, comment);
}
@@ -102,7 +101,7 @@ public class AttachCommentsVisitor extends AbstractVisitor {
switch (attachType) {
case INSN: {
InsnNode insn = getInsnByOffset(mth, codeRef.getIndex());
addComment(insn, comment);
addComment(insn, comment.getComment());
break;
}
default:
@@ -110,11 +109,11 @@ public class AttachCommentsVisitor extends AbstractVisitor {
}
}
private static void addComment(@Nullable IAttributeNode node, ICodeComment comment) {
private static void addComment(@Nullable IAttributeNode node, String comment) {
if (node == null) {
return;
}
node.addAttr(AType.CODE_COMMENTS, new CodeComment(comment));
node.addAttr(AType.CODE_COMMENTS, comment);
}
private List<ICodeComment> getCommentsData(ClassNode cls) {
@@ -1,6 +1,7 @@
package jadx.core.dex.visitors;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.jetbrains.annotations.Nullable;
@@ -15,12 +16,16 @@ import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.args.ArgType;
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.visitors.typeinference.TypeCompare;
import jadx.core.dex.visitors.typeinference.TypeCompareEnum;
import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.exceptions.JadxRuntimeException;
import static jadx.core.dex.visitors.ProcessInstructionsVisitor.getNextInsnOffset;
@@ -117,6 +122,7 @@ public class AttachTryCatchVisitor extends AbstractVisitor {
if (allHandlerOffset >= 0) {
Utils.addToList(list, createHandler(mth, insnByOffset, allHandlerOffset, null));
}
checkAndFilterHandlers(mth, list);
return list;
}
@@ -143,6 +149,45 @@ public class AttachTryCatchVisitor extends AbstractVisitor {
return handler;
}
private static void checkAndFilterHandlers(MethodNode mth, List<ExceptionHandler> list) {
if (list.size() <= 1) {
return;
}
// Remove shadowed handlers (with same or narrow type compared to previous)
TypeCompare typeCompare = mth.root().getTypeCompare();
Iterator<ExceptionHandler> it = list.iterator();
ArgType maxType = null;
while (it.hasNext()) {
ExceptionHandler handler = it.next();
ArgType maxCatch = maxCatchFromHandler(handler, typeCompare);
if (maxType == null) {
maxType = maxCatch;
} else {
TypeCompareEnum result = typeCompare.compareObjects(maxType, maxCatch);
if (result.isWiderOrEqual()) {
if (Consts.DEBUG_EXC_HANDLERS) {
LOG.debug("Removed shadowed catch handler: {}, from list: {}", handler, list);
}
it.remove();
}
}
}
}
private static ArgType maxCatchFromHandler(ExceptionHandler handler, TypeCompare typeCompare) {
List<ClassInfo> catchTypes = handler.getCatchTypes();
if (catchTypes.isEmpty()) {
return ArgType.THROWABLE;
}
if (catchTypes.size() == 1) {
return catchTypes.get(0).getType();
}
return catchTypes.stream()
.map(ClassInfo::getType)
.max(typeCompare.getComparator())
.orElseThrow(() -> new JadxRuntimeException("Failed to get max type from catch list: " + catchTypes));
}
private static InsnNode insertNOP(InsnNode[] insnByOffset, int offset) {
InsnNode nop = new InsnNode(InsnType.NOP, 0);
nop.setOffset(offset);
@@ -18,7 +18,7 @@ import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.args.SSAVar;
import jadx.core.dex.instructions.mods.ConstructorInsn;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.IFieldInfoRef;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.visitors.finaly.MarkFinallyVisitor;
@@ -26,7 +26,6 @@ import jadx.core.dex.visitors.ssa.SSATransform;
import jadx.core.dex.visitors.typeinference.TypeInferenceVisitor;
import jadx.core.utils.InsnRemover;
import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.exceptions.JadxRuntimeException;
@JadxVisitor(
name = "Constants Inline",
@@ -74,7 +73,8 @@ public class ConstInlineVisitor extends AbstractVisitor {
if (!constArg.isLiteral()) {
return;
}
if (constArg.isZeroLiteral() && forbidNullInlines(sVar)) {
long lit = ((LiteralArg) constArg).getLiteral();
if (lit == 0 && forbidNullInlines(sVar)) {
// all usages forbids inlining
return;
}
@@ -82,7 +82,7 @@ public class ConstInlineVisitor extends AbstractVisitor {
}
case CONST_STR: {
String s = ((ConstStringNode) insn).getString();
IFieldInfoRef f = mth.getParentClass().getConstField(s);
FieldNode f = mth.getParentClass().getConstField(s);
if (f == null) {
InsnNode copy = insn.copyWithoutResult();
constArg = InsnArg.wrapArg(copy);
@@ -90,7 +90,7 @@ public class ConstInlineVisitor extends AbstractVisitor {
InsnNode constGet = new IndexInsnNode(InsnType.SGET, f.getFieldInfo(), 0);
constArg = InsnArg.wrapArg(constGet);
constArg.setType(ArgType.STRING);
onSuccess = () -> ModVisitor.addFieldUsage(f, mth);
onSuccess = () -> f.addUseIn(mth);
}
break;
}
@@ -134,15 +134,20 @@ public class ConstInlineVisitor extends AbstractVisitor {
}
private static boolean forbidNullArgInline(InsnNode insn, RegisterArg useArg) {
if (insn.getType() == InsnType.MOVE) {
// result is null, chain checks
return forbidNullInlines(insn.getResult().getSVar());
switch (insn.getType()) {
case MOVE:
case CAST:
case CHECK_CAST:
// result is null, chain checks
return forbidNullInlines(insn.getResult().getSVar());
default:
if (!canUseNull(insn, useArg)) {
useArg.add(AFlag.DONT_INLINE_CONST);
return true;
}
return false;
}
if (!canUseNull(insn, useArg)) {
useArg.add(AFlag.DONT_INLINE_CONST);
return true;
}
return false;
}
private static boolean canUseNull(InsnNode insn, RegisterArg useArg) {
@@ -251,7 +256,7 @@ public class ConstInlineVisitor extends AbstractVisitor {
return false;
}
// arg replaced, made some optimizations
IFieldInfoRef fieldNode = null;
FieldNode fieldNode = null;
ArgType litArgType = litArg.getType();
if (litArgType.isTypeKnown()) {
fieldNode = mth.getParentClass().getConstFieldByLiteralArg(litArg);
@@ -261,10 +266,12 @@ public class ConstInlineVisitor extends AbstractVisitor {
if (fieldNode != null) {
IndexInsnNode sgetInsn = new IndexInsnNode(InsnType.SGET, fieldNode.getFieldInfo(), 0);
if (litArg.wrapInstruction(mth, sgetInsn) != null) {
ModVisitor.addFieldUsage(fieldNode, mth);
fieldNode.addUseIn(mth);
}
} else {
addExplicitCast(useInsn, litArg);
if (needExplicitCast(useInsn, litArg)) {
litArg.add(AFlag.EXPLICIT_PRIMITIVE_TYPE);
}
}
} else {
if (!useInsn.replaceArg(arg, constArg.duplicate())) {
@@ -275,33 +282,18 @@ public class ConstInlineVisitor extends AbstractVisitor {
return true;
}
private static void addExplicitCast(InsnNode insn, LiteralArg arg) {
private static boolean needExplicitCast(InsnNode insn, LiteralArg arg) {
if (insn instanceof BaseInvokeNode) {
BaseInvokeNode callInsn = (BaseInvokeNode) insn;
MethodInfo callMth = callInsn.getCallMth();
if (callInsn.getInstanceArg() == arg) {
// instance arg is null, force cast
if (!arg.isZeroLiteral()) {
throw new JadxRuntimeException("Unexpected instance arg in invoke");
}
ArgType castType = callMth.getDeclClass().getType();
InsnNode castInsn = new IndexInsnNode(InsnType.CAST, castType, 1);
castInsn.addArg(arg);
castInsn.add(AFlag.EXPLICIT_CAST);
InsnArg wrapCast = InsnArg.wrapArg(castInsn);
wrapCast.setType(castType);
insn.replaceArg(arg, wrapCast);
} else {
int offset = callInsn.getFirstArgOffset();
int argIndex = insn.getArgIndex(arg);
ArgType argType = callMth.getArgumentsTypes().get(argIndex - offset);
if (argType.isPrimitive()) {
arg.setType(argType);
if (argType.equals(ArgType.BYTE)) {
arg.add(AFlag.EXPLICIT_PRIMITIVE_TYPE);
}
}
int offset = callInsn.getFirstArgOffset();
int argIndex = insn.getArgIndex(arg);
ArgType argType = callMth.getArgumentsTypes().get(argIndex - offset);
if (argType.isPrimitive()) {
arg.setType(argType);
return argType.equals(ArgType.BYTE);
}
}
return false;
}
}
@@ -199,7 +199,7 @@ public class DotGraphVisitor extends AbstractVisitor {
dot.add("color=red,");
}
dot.add("label=\"{");
dot.add(String.valueOf(block.getId())).add("\\:\\ ");
dot.add(String.valueOf(block.getCId())).add("\\:\\ ");
dot.add(InsnUtils.formatOffset(block.getStartOffset()));
if (!attrs.isEmpty()) {
dot.add('|').add(attrs);
@@ -208,8 +208,6 @@ public class DotGraphVisitor extends AbstractVisitor {
dot.add('|');
dot.startLine("doms: ").add(escape(block.getDoms()));
dot.startLine("\\lidom: ").add(escape(block.getIDom()));
dot.startLine("\\lpost-doms: ").add(escape(block.getPostDoms()));
dot.startLine("\\lpost-idom: ").add(escape(block.getIPostDom()));
dot.startLine("\\ldom-f: ").add(escape(block.getDomFrontier()));
dot.startLine("\\ldoms-on: ").add(escape(Utils.listToString(block.getDominatesOn())));
dot.startLine("\\l");
@@ -232,10 +230,10 @@ public class DotGraphVisitor extends AbstractVisitor {
if (PRINT_DOMINATORS) {
for (BlockNode c : block.getDominatesOn()) {
conn.startLine(block.getId() + " -> " + c.getId() + "[color=green];");
conn.startLine(block.getCId() + " -> " + c.getCId() + "[color=green];");
}
for (BlockNode dom : BlockUtils.bitSetToBlocks(mth, block.getDomFrontier())) {
conn.startLine("f_" + block.getId() + " -> f_" + dom.getId() + "[color=blue];");
conn.startLine("f_" + block.getCId() + " -> f_" + dom.getCId() + "[color=blue];");
}
}
}
@@ -275,7 +273,7 @@ public class DotGraphVisitor extends AbstractVisitor {
private String makeName(IContainer c) {
String name;
if (c instanceof BlockNode) {
name = "Node_" + ((BlockNode) c).getId();
name = "Node_" + ((BlockNode) c).getCId();
} else if (c instanceof IBlock) {
name = "Node_" + c.getClass().getSimpleName() + '_' + c.hashCode();
} else {
@@ -319,7 +317,8 @@ public class DotGraphVisitor extends AbstractVisitor {
.replace("\"", "\\\"")
.replace("-", "\\-")
.replace("|", "\\|")
.replaceAll("\\R", NL);
.replace(ICodeWriter.NL, NL)
.replace("\n", NL);
}
}
}
@@ -99,7 +99,7 @@ public class MarkMethodsForInline extends AbstractVisitor {
&& retInsn.getArg(0).isSameVar(firstInsn.getResult())
&& firstInsn.getArg(0).isSameVar(mthRegs.get(0));
case SGET:
return mthRegs.isEmpty()
return mthRegs.size() == 0
&& retInsn.getArg(0).isSameVar(firstInsn.getResult());
case IPUT:
@@ -113,7 +113,7 @@ public class MarkMethodsForInline extends AbstractVisitor {
&& firstInsn.getArg(0).isSameVar(mthRegs.get(0));
case INVOKE:
return !mthRegs.isEmpty()
return mthRegs.size() >= 1
&& firstInsn.getArg(0).isSameVar(mthRegs.get(0))
&& retInsn.getArg(0).isSameVar(firstInsn.getResult());
default:
@@ -5,9 +5,7 @@ import java.util.List;
import java.util.Map;
import java.util.function.Function;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.ICodeWriter;
import jadx.core.Consts;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.info.MethodInfo;
@@ -30,7 +28,6 @@ import jadx.core.dex.visitors.methods.MutableMethodDetails;
import jadx.core.dex.visitors.shrink.CodeShrinkVisitor;
import jadx.core.dex.visitors.typeinference.TypeCompare;
import jadx.core.dex.visitors.typeinference.TypeCompareEnum;
import jadx.core.utils.InsnUtils;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxRuntimeException;
@@ -46,8 +43,6 @@ import jadx.core.utils.exceptions.JadxRuntimeException;
}
)
public class MethodInvokeVisitor extends AbstractVisitor {
private static final Logger LOG = LoggerFactory.getLogger(MethodInvokeVisitor.class);
private RootNode root;
@Override
@@ -121,8 +116,7 @@ public class MethodInvokeVisitor extends AbstractVisitor {
int argsOffset = invokeInsn.getFirstArgOffset();
List<ArgType> compilerVarTypes = collectCompilerVarTypes(invokeInsn, argsOffset);
List<ArgType> castTypes = searchCastTypes(parentMth, effectiveMthDetails, effectiveOverloadMethods, compilerVarTypes);
List<ArgType> resultCastTypes = expandTypes(parentMth, effectiveMthDetails, castTypes);
applyArgsCast(invokeInsn, argsOffset, compilerVarTypes, resultCastTypes);
applyArgsCast(invokeInsn, argsOffset, compilerVarTypes, castTypes);
}
/**
@@ -186,13 +180,7 @@ public class MethodInvokeVisitor extends AbstractVisitor {
if (arg.isLiteral() && compilerType.isPrimitive() && castType.isPrimitive()) {
arg.setType(castType);
arg.add(AFlag.EXPLICIT_PRIMITIVE_TYPE);
} else if (InsnUtils.isWrapped(arg, InsnType.CHECK_CAST)) {
IndexInsnNode wrapInsn = ((IndexInsnNode) ((InsnWrapArg) arg).getWrapInsn());
wrapInsn.updateIndex(castType);
} else {
if (Consts.DEBUG_TYPE_INFERENCE) {
LOG.info("Insert cast for invoke insn arg: {}, insn: {}", arg, invokeInsn);
}
InsnNode castInsn = new IndexInsnNode(InsnType.CAST, castType, 1);
castInsn.addArg(arg);
castInsn.add(AFlag.EXPLICIT_CAST);
@@ -255,7 +243,7 @@ public class MethodInvokeVisitor extends AbstractVisitor {
private List<ArgType> searchCastTypes(MethodNode parentMth, IMethodDetails mthDetails, List<IMethodDetails> overloadedMethods,
List<ArgType> compilerVarTypes) {
// try compiler types
// try compile types
if (isOverloadResolved(mthDetails, overloadedMethods, compilerVarTypes)) {
return compilerVarTypes;
}
@@ -289,10 +277,10 @@ public class MethodInvokeVisitor extends AbstractVisitor {
if (Consts.DEBUG_OVERLOADED_CASTS) {
// TODO: try to minimize casts count
parentMth.addDebugComment("Failed to find minimal casts for resolve overloaded methods, cast all args instead"
+ "\n method: " + mthDetails
+ "\n arg types: " + compilerVarTypes
+ "\n candidates:"
+ "\n " + Utils.listToString(overloadedMethods, "\n "));
+ ICodeWriter.NL + " method: " + mthDetails
+ ICodeWriter.NL + " arg types: " + compilerVarTypes
+ ICodeWriter.NL + " candidates:"
+ ICodeWriter.NL + " " + Utils.listToString(overloadedMethods, ICodeWriter.NL + " "));
}
// not resolved -> cast all args
return mthDetails.getArgTypes();
@@ -312,27 +300,6 @@ public class MethodInvokeVisitor extends AbstractVisitor {
return changed;
}
/**
* Use generified types if available
*/
private List<ArgType> expandTypes(MethodNode parentMth, IMethodDetails methodDetails, List<ArgType> castTypes) {
TypeCompare typeCompare = parentMth.root().getTypeCompare();
List<ArgType> mthArgTypes = methodDetails.getArgTypes();
int argsCount = castTypes.size();
List<ArgType> list = new ArrayList<>(argsCount);
for (int i = 0; i < argsCount; i++) {
ArgType mthType = mthArgTypes.get(i);
ArgType castType = castTypes.get(i);
TypeCompareEnum result = typeCompare.compareTypes(mthType, castType);
if (result == TypeCompareEnum.NARROW_BY_GENERIC) {
list.add(mthType);
} else {
list.add(castType);
}
}
return list;
}
private boolean isOverloadResolved(IMethodDetails expectedMthDetails, List<IMethodDetails> overloadedMethods, List<ArgType> castTypes) {
if (overloadedMethods.isEmpty()) {
return false;
@@ -420,22 +387,12 @@ public class MethodInvokeVisitor extends AbstractVisitor {
}
if (arg instanceof InsnWrapArg) {
InsnWrapArg wrapArg = (InsnWrapArg) arg;
return getInsnCompilerType(arg, wrapArg.getWrapInsn());
InsnNode wrapInsn = wrapArg.getWrapInsn();
if (wrapInsn.getResult() != null) {
return wrapInsn.getResult().getType();
}
return arg.getType();
}
throw new JadxRuntimeException("Unknown var type for: " + arg);
}
private static ArgType getInsnCompilerType(InsnArg arg, InsnNode insn) {
switch (insn.getType()) {
case CAST:
case CHECK_CAST:
return ((IndexInsnNode) insn).getIndexAsType();
default:
if (insn.getResult() != null) {
return insn.getResult().getType();
}
return arg.getType();
}
}
}
@@ -44,7 +44,6 @@ import jadx.core.dex.instructions.mods.TernaryInsn;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.IFieldInfoRef;
import jadx.core.dex.nodes.IMethodDetails;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
@@ -240,10 +239,10 @@ public class ModVisitor extends AbstractVisitor {
int[] keys = insn.getKeys();
int len = keys.length;
for (int k = 0; k < len; k++) {
IFieldInfoRef f = parentClass.getConstField(keys[k]);
FieldNode f = parentClass.getConstField(keys[k]);
if (f != null) {
insn.modifyKey(k, f);
addFieldUsage(f, mth);
f.addUseIn(mth);
}
}
}
@@ -314,7 +313,7 @@ public class ModVisitor extends AbstractVisitor {
}
return new EncodedValue(EncodedType.ENCODED_ARRAY, listVal);
}
IFieldInfoRef constField = parentCls.getConstField(encodedValue.getValue());
FieldNode constField = parentCls.getConstField(encodedValue.getValue());
if (constField != null) {
return new EncodedValue(EncodedType.ENCODED_FIELD, constField.getFieldInfo());
}
@@ -322,7 +321,7 @@ public class ModVisitor extends AbstractVisitor {
}
private static void replaceConst(MethodNode mth, ClassNode parentClass, BlockNode block, int i, InsnNode insn) {
IFieldInfoRef f;
FieldNode f;
if (insn.getType() == InsnType.CONST_STR) {
String s = ((ConstStringNode) insn).getString();
f = parentClass.getConstField(s);
@@ -336,7 +335,7 @@ public class ModVisitor extends AbstractVisitor {
InsnNode inode = new IndexInsnNode(InsnType.SGET, f.getFieldInfo(), 0);
inode.setResult(insn.getResult());
replaceInsn(mth, block, i, inode);
addFieldUsage(f, mth);
f.addUseIn(mth);
}
}
@@ -346,11 +345,11 @@ public class ModVisitor extends AbstractVisitor {
}
InsnArg litArg = arithNode.getArg(1);
if (litArg.isLiteral()) {
IFieldInfoRef f = parentClass.getConstFieldByLiteralArg((LiteralArg) litArg);
FieldNode f = parentClass.getConstFieldByLiteralArg((LiteralArg) litArg);
if (f != null) {
InsnNode fGet = new IndexInsnNode(InsnType.SGET, f.getFieldInfo(), 0);
if (arithNode.replaceArg(litArg, InsnArg.wrapArg(fGet))) {
addFieldUsage(f, mth);
f.addUseIn(mth);
}
}
}
@@ -390,11 +389,6 @@ public class ModVisitor extends AbstractVisitor {
private static void removeCheckCast(MethodNode mth, BlockNode block, int i, IndexInsnNode insn) {
InsnArg castArg = insn.getArg(0);
if (castArg.isZeroLiteral()) {
// always keep cast for 'null'
insn.add(AFlag.EXPLICIT_CAST);
return;
}
ArgType castType = (ArgType) insn.getIndex();
if (!ArgType.isCastNeeded(mth.root(), castArg.getType(), castType)) {
RegisterArg result = insn.getResult();
@@ -567,11 +561,11 @@ public class ModVisitor extends AbstractVisitor {
InsnNode filledArr = new FilledNewArrayNode(elType, list.size());
filledArr.setResult(newArrayNode.getResult().duplicate());
for (LiteralArg arg : list) {
IFieldInfoRef f = mth.getParentClass().getConstFieldByLiteralArg(arg);
FieldNode f = mth.getParentClass().getConstFieldByLiteralArg(arg);
if (f != null) {
InsnNode fGet = new IndexInsnNode(InsnType.SGET, f.getFieldInfo(), 0);
filledArr.addArg(InsnArg.wrapArg(fGet));
addFieldUsage(f, mth);
f.addUseIn(mth);
} else {
filledArr.addArg(arg.duplicate());
}
@@ -608,10 +602,4 @@ public class ModVisitor extends AbstractVisitor {
}
block.copyAttributeFrom(insn, AType.CODE_COMMENTS); // save comment
}
public static void addFieldUsage(IFieldInfoRef fieldData, MethodNode mth) {
if (fieldData instanceof FieldNode) {
((FieldNode) fieldData).addUseIn(mth);
}
}
}
@@ -25,9 +25,7 @@ import jadx.core.dex.attributes.nodes.LineAttrNode;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.instructions.ArithNode;
import jadx.core.dex.instructions.ArithOp;
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.InsnArg;
import jadx.core.dex.instructions.args.InsnWrapArg;
@@ -84,7 +82,6 @@ public class PrepareForCodeGen extends AbstractVisitor {
removeParenthesis(block);
modifyArith(block);
checkConstUsage(block);
addNullCasts(mth, block);
}
moveConstructorInConstructor(mth);
collectFieldsUsageInAnnotations(mth, mth);
@@ -382,27 +379,4 @@ public class PrepareForCodeGen extends AbstractVisitor {
break;
}
}
private void addNullCasts(MethodNode mth, BlockNode block) {
for (InsnNode insn : block.getInstructions()) {
switch (insn.getType()) {
case INVOKE:
verifyNullCast(mth, ((InvokeNode) insn).getInstanceArg());
break;
case ARRAY_LENGTH:
verifyNullCast(mth, insn.getArg(0));
break;
}
}
}
private void verifyNullCast(MethodNode mth, InsnArg arg) {
if (arg != null && arg.isZeroConst()) {
ArgType castType = arg.getType();
IndexInsnNode castInsn = new IndexInsnNode(InsnType.CAST, castType, 1);
castInsn.addArg(InsnArg.lit(0, castType));
arg.wrapInstruction(mth, castInsn);
}
}
}
@@ -43,7 +43,9 @@ public class ProcessAnonymous extends AbstractVisitor {
if (!inlineAnonymousClasses) {
return;
}
root.getClasses().forEach(ProcessAnonymous::processClass);
for (ClassNode cls : root.getClasses()) {
markAnonymousClass(cls);
}
mergeAnonymousDeps(root);
}
@@ -57,18 +59,10 @@ public class ProcessAnonymous extends AbstractVisitor {
}
private void visitClassAndInners(ClassNode cls) {
processClass(cls);
markAnonymousClass(cls);
cls.getInnerClasses().forEach(this::visitClassAndInners);
}
private static void processClass(ClassNode cls) {
try {
markAnonymousClass(cls);
} catch (Throwable e) {
cls.addError("Anonymous visitor error", e);
}
}
private static void markAnonymousClass(ClassNode cls) {
if (!canBeAnonymous(cls)) {
return;
@@ -279,10 +273,6 @@ public class ProcessAnonymous extends AbstractVisitor {
if (!ctrUseMth.getMethodInfo().isClassInit()) {
return false;
}
if (cls.getUseInMth().isEmpty()) {
// no outside usage, inline not needed
return false;
}
FieldNode instFld = ListUtils.filterOnlyOne(cls.getFields(),
f -> f.getAccessFlags().containsFlags(AccessFlags.PUBLIC, AccessFlags.STATIC, AccessFlags.FINAL)
&& f.getFieldInfo().getType().equals(cls.getClassInfo().getType()));
@@ -16,7 +16,7 @@ import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.LiteralArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.IFieldInfoRef;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.visitors.shrink.CodeShrinkVisitor;
@@ -167,11 +167,11 @@ public class ReplaceNewArray extends AbstractVisitor {
private static InsnArg replaceConstInArg(MethodNode mth, InsnArg valueArg) {
if (valueArg.isLiteral()) {
IFieldInfoRef f = mth.getParentClass().getConstFieldByLiteralArg((LiteralArg) valueArg);
FieldNode f = mth.getParentClass().getConstFieldByLiteralArg((LiteralArg) valueArg);
if (f != null) {
InsnNode fGet = new IndexInsnNode(InsnType.SGET, f.getFieldInfo(), 0);
InsnArg arg = InsnArg.wrapArg(fGet);
ModVisitor.addFieldUsage(f, mth);
f.addUseIn(mth);
return arg;
}
}
@@ -402,8 +402,7 @@ public class SimplifyVisitor extends AbstractVisitor {
}
}
if (!stringArgFound) {
String argStr = Utils.listToString(args, InsnArg::toShortString);
mth.addDebugComment("TODO: convert one arg to string using `String.valueOf()`, args: " + argStr);
mth.addDebugComment("TODO: convert one arg to string using `String.valueOf()`, args: " + args);
return null;
}
@@ -626,9 +625,7 @@ public class SimplifyVisitor extends AbstractVisitor {
for (int i = 1; i < argsCount; i++) {
concat.addArg(wrap.getArg(i));
}
InsnArg concatArg = InsnArg.wrapArg(concat);
concatArg.setType(ArgType.STRING);
return ArithNode.oneArgOp(ArithOp.ADD, fArg, concatArg);
return ArithNode.oneArgOp(ArithOp.ADD, fArg, InsnArg.wrapArg(concat));
} catch (Exception e) {
LOG.debug("Can't convert field arith insn: {}, mth: {}", insn, mth, e);
}
@@ -68,7 +68,7 @@ public class BlockExceptionHandler {
BlockProcessor.removeMarkedBlocks(mth);
BlockSet sorted = new BlockSet(mth);
BlockUtils.visitDFS(mth, sorted::set);
BlockUtils.dfsVisit(mth, sorted::set);
removeUnusedExcHandlers(mth, tryBlocks, sorted);
return true;
}
@@ -331,17 +331,6 @@ public class BlockExceptionHandler {
return false;
}
BlockNode bottom = searchBottomBlock(mth, blocks);
BlockNode splitReturn;
if (bottom != null && bottom.isReturnBlock()) {
if (Consts.DEBUG_EXC_HANDLERS) {
LOG.debug("TryCatch #{} bottom block ({}) is return, split", tryCatchBlock.id(), bottom);
}
splitReturn = bottom;
bottom = BlockSplitter.blockSplitTop(mth, bottom);
bottom.add(AFlag.SYNTHETIC);
} else {
splitReturn = null;
}
if (Consts.DEBUG_EXC_HANDLERS) {
LOG.debug("TryCatch #{} split: top {}, bottom: {}", tryCatchBlock.id(), top, bottom);
}
@@ -360,18 +349,6 @@ public class BlockExceptionHandler {
bottomSplitterBlock.add(AFlag.EXC_BOTTOM_SPLITTER);
bottomSplitterBlock.add(AFlag.SYNTHETIC);
BlockSplitter.connect(bottom, bottomSplitterBlock);
if (splitReturn != null) {
// redirect handler to return block instead synthetic split block to avoid self-loop
BlockSet bottomPreds = BlockSet.from(mth, bottom.getPredecessors());
for (ExceptionHandler handler : tryCatchBlock.getHandlers()) {
if (bottomPreds.intersects(handler.getBlocks())) {
BlockNode lastBlock = bottomPreds.intersect(handler.getBlocks()).getOne();
if (lastBlock != null) {
BlockSplitter.replaceConnection(lastBlock, bottom, splitReturn);
}
}
}
}
}
if (Consts.DEBUG_EXC_HANDLERS) {
@@ -623,7 +600,7 @@ public class BlockExceptionHandler {
for (ExceptionHandler eh : mth.getExceptionHandlers()) {
boolean notProcessed = true;
BlockNode handlerBlock = eh.getHandlerBlock();
if (handlerBlock == null || blocks.get(handlerBlock)) {
if (blocks.get(handlerBlock)) {
continue;
}
for (TryCatchBlockAttr tcb : tryBlocks) {
@@ -70,8 +70,6 @@ public class BlockProcessor extends AbstractVisitor {
registerLoops(mth);
processNestedLoops(mth);
PostDominatorTree.compute(mth);
updateCleanSuccessors(mth);
if (!mth.contains(AFlag.DISABLE_BLOCKS_LOCK)) {
mth.finishBasicBlocks();
@@ -3,7 +3,8 @@ package jadx.core.dex.visitors.blocks;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.List;
import java.util.function.Function;
import org.jetbrains.annotations.NotNull;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.MethodNode;
@@ -22,14 +23,14 @@ public class DominatorTree {
public static void compute(MethodNode mth) {
List<BlockNode> sorted = sortBlocks(mth);
BlockNode[] doms = build(sorted, BlockNode::getPredecessors);
BlockNode[] doms = build(sorted);
apply(sorted, doms);
}
private static List<BlockNode> sortBlocks(MethodNode mth) {
int blocksCount = mth.getBasicBlocks().size();
List<BlockNode> sorted = new ArrayList<>(blocksCount);
BlockUtils.visitDFS(mth, sorted::add);
BlockUtils.dfsVisit(mth, sorted::add);
if (sorted.size() != blocksCount) {
throw new JadxRuntimeException("Found unreachable blocks");
}
@@ -37,7 +38,8 @@ public class DominatorTree {
return sorted;
}
static BlockNode[] build(List<BlockNode> sorted, Function<BlockNode, List<BlockNode>> predFunc) {
@NotNull
private static BlockNode[] build(List<BlockNode> sorted) {
int blocksCount = sorted.size();
BlockNode[] doms = new BlockNode[blocksCount];
doms[0] = sorted.get(0);
@@ -46,7 +48,7 @@ public class DominatorTree {
changed = false;
for (int blockId = 1; blockId < blocksCount; blockId++) {
BlockNode b = sorted.get(blockId);
List<BlockNode> preds = predFunc.apply(b);
List<BlockNode> preds = b.getPredecessors();
int pickedPred = -1;
BlockNode newIDom = null;
for (BlockNode pred : preds) {
@@ -58,7 +60,7 @@ public class DominatorTree {
}
}
if (newIDom == null) {
throw new JadxRuntimeException("No immediate dominator for block: " + b);
throw new JadxRuntimeException("No predecessors for block: " + b);
}
for (BlockNode predBlock : preds) {
int predId = predBlock.getId();
@@ -108,7 +110,7 @@ public class DominatorTree {
}
}
static BitSet collectDoms(BlockNode[] doms, BlockNode idom) {
private static BitSet collectDoms(BlockNode[] doms, BlockNode idom) {
BitSet domBS = new BitSet(doms.length);
BlockNode nextIDom = idom;
while (true) {
@@ -22,7 +22,8 @@ public class FixMultiEntryLoops {
}
List<SpecialEdgeAttr> specialEdges = mth.getAll(AType.SPECIAL_EDGE);
List<SpecialEdgeAttr> multiEntryLoops = specialEdges.stream()
.filter(e -> e.getType() == SpecialEdgeType.BACK_EDGE && !isSingleEntryLoop(e))
.filter(e -> e.getType() == SpecialEdgeType.BACK_EDGE)
.filter(e -> !isSingleEntryLoop(e))
.collect(Collectors.toList());
if (multiEntryLoops.isEmpty()) {
return false;
@@ -41,21 +42,12 @@ public class FixMultiEntryLoops {
}
private static boolean fixLoop(MethodNode mth, SpecialEdgeAttr backEdge, List<SpecialEdgeAttr> crossEdges) {
if (isHeaderSuccessorEntry(mth, backEdge, crossEdges)) {
return true;
}
if (isEndBlockEntry(mth, backEdge, crossEdges)) {
return true;
}
mth.addWarnComment("Unsupported multi-entry loop pattern (" + backEdge + "). Please report as a decompilation issue!!!");
return false;
}
private static boolean isHeaderSuccessorEntry(MethodNode mth, SpecialEdgeAttr backEdge, List<SpecialEdgeAttr> crossEdges) {
BlockNode header = backEdge.getEnd();
BlockNode headerIDom = header.getIDom();
SpecialEdgeAttr subEntry = ListUtils.filterOnlyOne(crossEdges, e -> e.getStart() == headerIDom);
if (subEntry == null || !ListUtils.isSingleElement(header.getSuccessors(), subEntry.getEnd())) {
if (subEntry == null || !isSupportedPattern(header, subEntry)) {
// TODO: for now only sub entry in header successor is supported
mth.addWarnComment("Unsupported multi-entry loop pattern (" + backEdge + "). Please submit an issue!!!");
return false;
}
BlockNode loopEnd = backEdge.getStart();
@@ -63,28 +55,12 @@ public class FixMultiEntryLoops {
BlockNode copyHeader = BlockSplitter.insertBlockBetween(mth, loopEnd, header);
BlockSplitter.copyBlockData(header, copyHeader);
BlockSplitter.replaceConnection(copyHeader, header, subEntryBlock);
mth.addDebugComment("Duplicate block (" + header + ") to fix multi-entry loop: " + backEdge);
mth.addDebugComment("Duplicate block to fix multi-entry loop: " + backEdge);
return true;
}
private static boolean isEndBlockEntry(MethodNode mth, SpecialEdgeAttr backEdge, List<SpecialEdgeAttr> crossEdges) {
BlockNode loopEnd = backEdge.getStart();
SpecialEdgeAttr subEntry = ListUtils.filterOnlyOne(crossEdges, e -> e.getEnd() == loopEnd);
if (subEntry == null) {
return false;
}
dupPath(mth, subEntry.getStart(), loopEnd, backEdge.getEnd());
mth.addDebugComment("Duplicate block (" + loopEnd + ") to fix multi-entry loop: " + backEdge);
return true;
}
/**
* Duplicate 'center' block on path from 'start' to 'end'
*/
private static void dupPath(MethodNode mth, BlockNode start, BlockNode center, BlockNode end) {
BlockNode copyCenter = BlockSplitter.insertBlockBetween(mth, start, end);
BlockSplitter.copyBlockData(center, copyCenter);
BlockSplitter.removeConnection(start, center);
private static boolean isSupportedPattern(BlockNode header, SpecialEdgeAttr subEntry) {
return ListUtils.isSingleElement(header.getSuccessors(), subEntry.getEnd());
}
private static boolean isSingleEntryLoop(SpecialEdgeAttr e) {
@@ -99,18 +75,21 @@ public class FixMultiEntryLoops {
}
private static void detectSpecialEdges(MethodNode mth) {
BlockColor[] colors = new BlockColor[mth.getBasicBlocks().size()];
List<BlockNode> blocks = mth.getBasicBlocks();
BlockColor[] colors = new BlockColor[blocks.size()];
Arrays.fill(colors, BlockColor.WHITE);
colorDFS(mth, colors, mth.getEnterBlock());
colorDFS(mth, blocks, colors, mth.getEnterBlock().getId());
}
// TODO: transform to non-recursive form
private static void colorDFS(MethodNode mth, BlockColor[] colors, BlockNode block) {
colors[block.getId()] = BlockColor.GRAY;
private static void colorDFS(MethodNode mth, List<BlockNode> blocks, BlockColor[] colors, int cur) {
colors[cur] = BlockColor.GRAY;
BlockNode block = blocks.get(cur);
for (BlockNode v : block.getSuccessors()) {
switch (colors[v.getId()]) {
int vId = v.getId();
switch (colors[vId]) {
case WHITE:
colorDFS(mth, colors, v);
colorDFS(mth, blocks, colors, vId);
break;
case GRAY:
mth.addAttr(AType.SPECIAL_EDGE, new SpecialEdgeAttr(SpecialEdgeType.BACK_EDGE, block, v));
@@ -120,6 +99,6 @@ public class FixMultiEntryLoops {
break;
}
}
colors[block.getId()] = BlockColor.BLACK;
colors[cur] = BlockColor.BLACK;
}
}
@@ -1,72 +0,0 @@
package jadx.core.dex.visitors.blocks;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.List;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.BlockUtils;
import jadx.core.utils.EmptyBitSet;
public class PostDominatorTree {
public static void compute(MethodNode mth) {
if (!mth.contains(AFlag.COMPUTE_POST_DOM)) {
return;
}
try {
int mthBlocksCount = mth.getBasicBlocks().size();
List<BlockNode> sorted = new ArrayList<>(mthBlocksCount);
BlockUtils.visitReverseDFS(mth, sorted::add);
// temporary set block ids to match reverse sorted order
// save old ids for later remapping
int blocksCount = sorted.size();
int[] ids = new int[mthBlocksCount];
for (int i = 0; i < blocksCount; i++) {
ids[i] = sorted.get(i).getId();
}
mth.updateBlockIds(sorted);
BlockNode[] postDoms = DominatorTree.build(sorted, BlockNode::getSuccessors);
BlockNode firstBlock = sorted.get(0);
firstBlock.setPostDoms(EmptyBitSet.EMPTY);
firstBlock.setIPostDom(null);
for (int i = 1; i < blocksCount; i++) {
BlockNode block = sorted.get(i);
BlockNode iPostDom = postDoms[i];
block.setIPostDom(iPostDom);
BitSet postDomBS = DominatorTree.collectDoms(postDoms, iPostDom);
block.setPostDoms(postDomBS);
}
for (int i = 1; i < blocksCount; i++) {
BlockNode block = sorted.get(i);
BitSet bs = new BitSet(blocksCount);
block.getPostDoms().stream().forEach(n -> bs.set(ids[n]));
bs.clear(ids[i]);
block.setPostDoms(bs);
}
// check for missing blocks in 'sorted' list
// can be caused by infinite loops
int blocksDelta = mthBlocksCount - blocksCount;
if (blocksDelta != 0) {
int insnsCount = 0;
for (BlockNode block : mth.getBasicBlocks()) {
if (block.getPostDoms() == null) {
block.setPostDoms(EmptyBitSet.EMPTY);
block.setIPostDom(null);
insnsCount += block.getInstructions().size();
}
}
mth.addInfoComment("Infinite loop detected, blocks: " + blocksDelta + ", insns: " + insnsCount);
}
} catch (Throwable e) {
// show error as a warning because this info not always used
mth.addWarnComment("Failed to build post-dominance tree", e);
} finally {
// revert block ids change
mth.updateBlockIds(mth.getBasicBlocks());
}
}
}
@@ -89,7 +89,7 @@ public class DebugInfoApplyVisitor extends AbstractVisitor {
return;
}
OptionalInt max = ssaVar.getUseList().stream().mapToInt(DebugInfoApplyVisitor::getInsnOffsetByArg).max();
if (max.isEmpty()) {
if (!max.isPresent()) {
return;
}
int startOffset = getInsnOffsetByArg(ssaVar.getAssign());
@@ -1,45 +0,0 @@
package jadx.core.dex.visitors.prepare;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.ConstStorage;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.visitors.AbstractVisitor;
import jadx.core.dex.visitors.JadxVisitor;
import jadx.core.utils.android.AndroidResourcesMap;
import jadx.core.utils.exceptions.JadxException;
// TODO: move this pass to separate "Android plugin"
@JadxVisitor(
name = "AddAndroidConstants",
desc = "Insert Android constants from resource mapping file",
runBefore = {
CollectConstValues.class
}
)
public class AddAndroidConstants extends AbstractVisitor {
private static final String R_CLS = "android.R";
private static final String R_INNER_CLS = R_CLS + '$';
@Override
public void init(RootNode root) throws JadxException {
if (!root.getArgs().isReplaceConsts()) {
return;
}
if (root.resolveClass(R_CLS) != null) {
// Android R class already loaded
return;
}
ConstStorage constStorage = root.getConstValues();
AndroidResourcesMap.getMap().forEach((resId, path) -> {
int sep = path.indexOf('/');
String clsName = R_INNER_CLS + path.substring(0, sep);
String resName = path.substring(sep + 1);
ClassInfo cls = ClassInfo.fromName(root, clsName);
FieldInfo field = FieldInfo.from(root, cls, resName, ArgType.INT);
constStorage.addGlobalConstField(field, resId);
});
}
}
@@ -1,55 +0,0 @@
package jadx.core.dex.visitors.prepare;
import org.jetbrains.annotations.Nullable;
import jadx.api.plugins.input.data.annotations.EncodedValue;
import jadx.api.plugins.input.data.attributes.JadxAttrType;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.info.ConstStorage;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.visitors.AbstractVisitor;
import jadx.core.dex.visitors.JadxVisitor;
import jadx.core.utils.exceptions.JadxException;
@JadxVisitor(
name = "CollectConstValues",
desc = "Collect and store values from static final fields"
)
public class CollectConstValues extends AbstractVisitor {
@Override
public boolean visit(ClassNode cls) throws JadxException {
RootNode root = cls.root();
if (!root.getArgs().isReplaceConsts()) {
return true;
}
if (cls.getFields().isEmpty()) {
return true;
}
ConstStorage constStorage = root.getConstValues();
for (FieldNode fld : cls.getFields()) {
try {
Object value = getFieldConstValue(fld);
if (value != null) {
constStorage.addConstField(fld, value, fld.getAccessFlags().isPublic());
}
} catch (Exception e) {
cls.addWarnComment("Failed to process value of field: " + fld, e);
}
}
return true;
}
public static @Nullable Object getFieldConstValue(FieldNode fld) {
AccessInfo accFlags = fld.getAccessFlags();
if (accFlags.isStatic() && accFlags.isFinal()) {
EncodedValue constVal = fld.get(JadxAttrType.CONSTANT_VALUE);
if (constVal != null && constVal != EncodedValue.NULL) {
return constVal.getValue();
}
}
return null;
}
}

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