refactor: add ErrorProne and NullAway checks, fix some issues
This commit is contained in:
@@ -39,5 +39,7 @@ jadx-output/
|
|||||||
*.orig
|
*.orig
|
||||||
quark.json
|
quark.json
|
||||||
|
|
||||||
|
.env
|
||||||
|
|
||||||
cliff.toml
|
cliff.toml
|
||||||
jadx-gui/src/main/resources/logback.xml
|
jadx-gui/src/main/resources/logback.xml
|
||||||
|
|||||||
+46
-2
@@ -11,7 +11,9 @@ plugins {
|
|||||||
id("com.diffplug.spotless") version "8.4.0"
|
id("com.diffplug.spotless") version "8.4.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
val jadxVersion by extra { System.getenv("JADX_VERSION") ?: "dev" }
|
val jadxEnv = loadEnv(file("$rootDir/.env"))
|
||||||
|
|
||||||
|
val jadxVersion by extra { jadxEnv["JADX_VERSION"] ?: "dev" }
|
||||||
println("jadx version: $jadxVersion")
|
println("jadx version: $jadxVersion")
|
||||||
version = jadxVersion
|
version = jadxVersion
|
||||||
|
|
||||||
@@ -19,7 +21,7 @@ val jadxBuildJavaVersion by extra { getBuildJavaVersion() }
|
|||||||
|
|
||||||
fun getBuildJavaVersion(): Int? {
|
fun getBuildJavaVersion(): Int? {
|
||||||
val envVarName = "JADX_BUILD_JAVA_VERSION"
|
val envVarName = "JADX_BUILD_JAVA_VERSION"
|
||||||
val buildJavaVer = System.getenv(envVarName)?.toInt() ?: return null
|
val buildJavaVer = jadxEnv[envVarName]?.toInt() ?: return null
|
||||||
if (buildJavaVer < 11) {
|
if (buildJavaVer < 11) {
|
||||||
throw GradleException("'$envVarName' can't be set to lower than 11")
|
throw GradleException("'$envVarName' can't be set to lower than 11")
|
||||||
}
|
}
|
||||||
@@ -27,6 +29,24 @@ fun getBuildJavaVersion(): Int? {
|
|||||||
return buildJavaVer
|
return buildJavaVer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// control ErrorProne checks level, can be: off, warn, error
|
||||||
|
val jadxBuildChecksMode: String by extra { getBuildChecksMode() }
|
||||||
|
|
||||||
|
fun getBuildChecksMode(): String {
|
||||||
|
val buildChecksMode = jadxEnv["JADX_BUILD_CHECKS_MODE"]?.lowercase() ?: "off"
|
||||||
|
val expectedValues = listOf("off", "warn", "error")
|
||||||
|
if (!expectedValues.contains(buildChecksMode)) {
|
||||||
|
throw GradleException("Unknown check mode: '$buildChecksMode', should be one of $expectedValues")
|
||||||
|
}
|
||||||
|
if (buildChecksMode != "off") {
|
||||||
|
val javaVersion = jadxBuildJavaVersion?.let { JavaVersion.toVersion(it) } ?: JavaVersion.current()
|
||||||
|
if (!javaVersion.isCompatibleWith(JavaVersion.VERSION_21)) {
|
||||||
|
throw GradleException("Error Prone requires Java 21")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return buildChecksMode
|
||||||
|
}
|
||||||
|
|
||||||
allprojects {
|
allprojects {
|
||||||
apply(plugin = "java")
|
apply(plugin = "java")
|
||||||
apply(plugin = "checkstyle")
|
apply(plugin = "checkstyle")
|
||||||
@@ -82,6 +102,30 @@ fun isNonStable(version: String): Boolean {
|
|||||||
return isStable.not()
|
return isStable.not()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun loadEnv(file: File): Map<String, String> {
|
||||||
|
val envMap = HashMap<String, String>()
|
||||||
|
System
|
||||||
|
.getenv()
|
||||||
|
.filter { it.key.startsWith("JADX_") }
|
||||||
|
.forEach { envMap[it.key] = it.value }
|
||||||
|
if (file.exists()) {
|
||||||
|
file
|
||||||
|
.readLines()
|
||||||
|
.map { it.trim() }
|
||||||
|
.filter { it.isNotEmpty() && !it.startsWith("#") }
|
||||||
|
.forEach {
|
||||||
|
val (k, v) = it.split("=", limit = 2)
|
||||||
|
envMap[k.trim()] = v.trim()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println(
|
||||||
|
"Loaded env vars (${envMap.size}):\n${
|
||||||
|
envMap.toList().sortedBy { it.first }.joinToString(separator = "\n") { "${it.first}=${it.second}" }
|
||||||
|
}\n",
|
||||||
|
)
|
||||||
|
return envMap
|
||||||
|
}
|
||||||
|
|
||||||
val distWinConfiguration: Configuration by configurations.creating {
|
val distWinConfiguration: Configuration by configurations.creating {
|
||||||
isCanBeConsumed = false
|
isCanBeConsumed = false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ dependencies {
|
|||||||
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:2.3.10")
|
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:2.3.10")
|
||||||
|
|
||||||
implementation("org.openrewrite:plugin:6.19.1")
|
implementation("org.openrewrite:plugin:6.19.1")
|
||||||
|
implementation("net.ltgt.errorprone:net.ltgt.errorprone.gradle.plugin:4.2.0")
|
||||||
|
implementation("net.ltgt.nullaway:net.ltgt.nullaway.gradle.plugin:2.3.0")
|
||||||
}
|
}
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
|
|||||||
@@ -1,14 +1,20 @@
|
|||||||
import org.gradle.api.tasks.testing.logging.TestExceptionFormat
|
import org.gradle.api.tasks.testing.logging.TestExceptionFormat
|
||||||
|
import net.ltgt.gradle.errorprone.CheckSeverity
|
||||||
|
import net.ltgt.gradle.errorprone.errorprone
|
||||||
|
import net.ltgt.gradle.nullaway.nullaway
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
java
|
java
|
||||||
checkstyle
|
checkstyle
|
||||||
|
|
||||||
id("jadx-rewrite")
|
id("jadx-rewrite")
|
||||||
|
id("net.ltgt.errorprone")
|
||||||
|
id("net.ltgt.nullaway")
|
||||||
}
|
}
|
||||||
|
|
||||||
val jadxVersion: String by rootProject.extra
|
val jadxVersion: String by rootProject.extra
|
||||||
val jadxBuildJavaVersion: Int? by rootProject.extra
|
val jadxBuildJavaVersion: Int? by rootProject.extra
|
||||||
|
val jadxBuildChecksMode: String by rootProject.extra
|
||||||
|
|
||||||
group = "io.github.skylot"
|
group = "io.github.skylot"
|
||||||
version = jadxVersion
|
version = jadxVersion
|
||||||
@@ -24,6 +30,9 @@ dependencies {
|
|||||||
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
|
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
|
||||||
|
|
||||||
testCompileOnly("org.jetbrains:annotations:26.1.0")
|
testCompileOnly("org.jetbrains:annotations:26.1.0")
|
||||||
|
|
||||||
|
errorprone("com.google.errorprone:error_prone_core:2.48.0")
|
||||||
|
errorprone("com.uber.nullaway:nullaway:0.13.1")
|
||||||
}
|
}
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
@@ -62,3 +71,27 @@ tasks {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tasks.withType<JavaCompile>().configureEach {
|
||||||
|
val checkEnabled = jadxBuildChecksMode != "off"
|
||||||
|
if (checkEnabled) {
|
||||||
|
options.compilerArgs.add("-XDaddTypeAnnotationsToSymbol=true")
|
||||||
|
}
|
||||||
|
options.errorprone {
|
||||||
|
isEnabled = checkEnabled
|
||||||
|
allErrorsAsWarnings = jadxBuildChecksMode == "warn"
|
||||||
|
excludedPaths = ".*/test/.*"
|
||||||
|
nullaway {
|
||||||
|
if (jadxBuildChecksMode == "error") {
|
||||||
|
error()
|
||||||
|
}
|
||||||
|
annotatedPackages.add("jadx")
|
||||||
|
}
|
||||||
|
// TODO: fix and enable all checks
|
||||||
|
disable("MixedMutabilityReturnType")
|
||||||
|
disable("EqualsGetClass")
|
||||||
|
disable("OperatorPrecedence")
|
||||||
|
disable("UnusedVariable")
|
||||||
|
disable("ImmutableEnumChecker")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
package jadx.commons.app;
|
package jadx.commons.app;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
public class JadxCommonEnv {
|
public class JadxCommonEnv {
|
||||||
|
|
||||||
public static String get(String varName, String defValue) {
|
public static @Nullable String get(String varName, @Nullable String defValue) {
|
||||||
String strValue = System.getenv(varName);
|
String strValue = System.getenv(varName);
|
||||||
return isNullOrEmpty(strValue) ? defValue : strValue;
|
return isNullOrEmpty(strValue) ? defValue : strValue;
|
||||||
}
|
}
|
||||||
@@ -23,7 +25,7 @@ public class JadxCommonEnv {
|
|||||||
return Integer.parseInt(strValue);
|
return Integer.parseInt(strValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isNullOrEmpty(String value) {
|
private static boolean isNullOrEmpty(@Nullable String value) {
|
||||||
return value == null || value.isEmpty();
|
return value == null || value.isEmpty();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ package jadx.commons.app;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.function.Function;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@@ -30,40 +31,39 @@ public class JadxCommonFiles {
|
|||||||
|
|
||||||
static {
|
static {
|
||||||
DirsLoader loader = new DirsLoader();
|
DirsLoader loader = new DirsLoader();
|
||||||
loader.init();
|
|
||||||
CONFIG_DIR = loader.getConfigDir();
|
CONFIG_DIR = loader.getConfigDir();
|
||||||
CACHE_DIR = loader.getCacheDir();
|
CACHE_DIR = loader.getCacheDir();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class DirsLoader {
|
private static final class DirsLoader {
|
||||||
private @Nullable ProjectDirectories dirs;
|
private final Path configDir;
|
||||||
private Path configDir;
|
private final Path cacheDir;
|
||||||
private Path cacheDir;
|
|
||||||
|
|
||||||
public void init() {
|
DirsLoader() {
|
||||||
try {
|
try {
|
||||||
configDir = loadEnvDir("JADX_CONFIG_DIR", pd -> pd.configDir);
|
AtomicReference<@Nullable ProjectDirectories> pdRef = new AtomicReference<>();
|
||||||
cacheDir = loadEnvDir("JADX_CACHE_DIR", pd -> pd.cacheDir);
|
configDir = loadEnvDir("JADX_CONFIG_DIR", () -> loadDirs(pdRef).configDir);
|
||||||
|
cacheDir = loadEnvDir("JADX_CACHE_DIR", () -> loadDirs(pdRef).cacheDir);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException("Failed to init common directories", e);
|
throw new RuntimeException("Failed to init common directories", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Path loadEnvDir(String envVar, Function<ProjectDirectories, String> dirFunc) throws IOException {
|
private static Path loadEnvDir(String envVar, Supplier<String> dirFunc) throws IOException {
|
||||||
String envDir = JadxCommonEnv.get(envVar, null);
|
String envDir = JadxCommonEnv.get(envVar, null);
|
||||||
String dirStr;
|
String dirStr;
|
||||||
if (envDir != null) {
|
if (envDir != null) {
|
||||||
dirStr = envDir;
|
dirStr = envDir;
|
||||||
} else {
|
} else {
|
||||||
dirStr = dirFunc.apply(loadDirs());
|
dirStr = dirFunc.get();
|
||||||
}
|
}
|
||||||
Path path = Path.of(dirStr).toAbsolutePath();
|
Path path = Path.of(dirStr).toAbsolutePath();
|
||||||
Files.createDirectories(path);
|
Files.createDirectories(path);
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized ProjectDirectories loadDirs() {
|
private static ProjectDirectories loadDirs(AtomicReference<@Nullable ProjectDirectories> pdRef) {
|
||||||
ProjectDirectories currentDirs = dirs;
|
ProjectDirectories currentDirs = pdRef.get();
|
||||||
if (currentDirs != null) {
|
if (currentDirs != null) {
|
||||||
return currentDirs;
|
return currentDirs;
|
||||||
}
|
}
|
||||||
@@ -76,7 +76,7 @@ public class JadxCommonFiles {
|
|||||||
LOG.debug("Loaded system dirs ({}ms): config: {}, cache: {}",
|
LOG.debug("Loaded system dirs ({}ms): config: {}, cache: {}",
|
||||||
System.currentTimeMillis() - start, loadedDirs.configDir, loadedDirs.cacheDir);
|
System.currentTimeMillis() - start, loadedDirs.configDir, loadedDirs.cacheDir);
|
||||||
}
|
}
|
||||||
dirs = loadedDirs;
|
pdRef.set(loadedDirs);
|
||||||
return loadedDirs;
|
return loadedDirs;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,11 +95,11 @@ public class JadxCommonFiles {
|
|||||||
return impl;
|
return impl;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Path getCacheDir() {
|
Path getCacheDir() {
|
||||||
return cacheDir;
|
return cacheDir;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Path getConfigDir() {
|
Path getConfigDir() {
|
||||||
return configDir;
|
return configDir;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package jadx.zip;
|
package jadx.zip;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@@ -9,6 +10,7 @@ import java.util.function.Function;
|
|||||||
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import jadx.zip.fallback.FallbackException;
|
||||||
import jadx.zip.fallback.FallbackZipParser;
|
import jadx.zip.fallback.FallbackZipParser;
|
||||||
import jadx.zip.parser.JadxZipParser;
|
import jadx.zip.parser.JadxZipParser;
|
||||||
import jadx.zip.security.IJadxZipSecurity;
|
import jadx.zip.security.IJadxZipSecurity;
|
||||||
@@ -39,13 +41,15 @@ public class ZipReader {
|
|||||||
|
|
||||||
@SuppressWarnings("resource")
|
@SuppressWarnings("resource")
|
||||||
public ZipContent open(File zipFile) throws IOException {
|
public ZipContent open(File zipFile) throws IOException {
|
||||||
|
if (!zipFile.exists()) {
|
||||||
|
throw new FileNotFoundException(zipFile.getAbsolutePath());
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
JadxZipParser jadxParser = new JadxZipParser(zipFile, options);
|
JadxZipParser jadxParser = new JadxZipParser(zipFile, options);
|
||||||
IZipParser detectedParser = detectParser(zipFile, jadxParser);
|
IZipParser detectedParser = detectParser(zipFile, jadxParser);
|
||||||
if (detectedParser != jadxParser) {
|
|
||||||
jadxParser.close();
|
|
||||||
}
|
|
||||||
return detectedParser.open();
|
return detectedParser.open();
|
||||||
|
} catch (FallbackException e) {
|
||||||
|
throw e;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
if (options.getFlags().contains(ZipReaderFlags.DONT_USE_FALLBACK)) {
|
if (options.getFlags().contains(ZipReaderFlags.DONT_USE_FALLBACK)) {
|
||||||
throw new IOException("Failed to open zip: " + zipFile, e);
|
throw new IOException("Failed to open zip: " + zipFile, e);
|
||||||
@@ -90,7 +94,7 @@ public class ZipReader {
|
|||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
private IZipParser detectParser(File zipFile, JadxZipParser jadxParser) {
|
private IZipParser detectParser(File zipFile, JadxZipParser jadxParser) throws IOException {
|
||||||
if (zipFile.getName().endsWith(".apk")
|
if (zipFile.getName().endsWith(".apk")
|
||||||
|| options.getFlags().contains(ZipReaderFlags.DONT_USE_FALLBACK)) {
|
|| options.getFlags().contains(ZipReaderFlags.DONT_USE_FALLBACK)) {
|
||||||
return jadxParser;
|
return jadxParser;
|
||||||
@@ -105,7 +109,7 @@ public class ZipReader {
|
|||||||
return jadxParser;
|
return jadxParser;
|
||||||
}
|
}
|
||||||
|
|
||||||
private FallbackZipParser buildFallbackParser(File zipFile) {
|
private FallbackZipParser buildFallbackParser(File zipFile) throws IOException {
|
||||||
return new FallbackZipParser(zipFile, options);
|
return new FallbackZipParser(zipFile, options);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package jadx.zip.fallback;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class FallbackException extends IOException {
|
||||||
|
public FallbackException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,39 +22,45 @@ import jadx.zip.security.IJadxZipSecurity;
|
|||||||
|
|
||||||
public class FallbackZipParser implements IZipParser {
|
public class FallbackZipParser implements IZipParser {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(FallbackZipParser.class);
|
private static final Logger LOG = LoggerFactory.getLogger(FallbackZipParser.class);
|
||||||
|
|
||||||
private final File file;
|
private final File file;
|
||||||
|
private final ZipFile zipFile;
|
||||||
private final IJadxZipSecurity zipSecurity;
|
private final IJadxZipSecurity zipSecurity;
|
||||||
private final boolean useLimitedDataStream;
|
private final boolean useLimitedDataStream;
|
||||||
|
|
||||||
private ZipFile zipFile;
|
public FallbackZipParser(File file, ZipReaderOptions options) throws FallbackException {
|
||||||
|
try {
|
||||||
public FallbackZipParser(File file, ZipReaderOptions options) {
|
this.file = file;
|
||||||
this.file = file;
|
this.zipFile = new ZipFile(file);
|
||||||
this.zipSecurity = options.getZipSecurity();
|
this.zipSecurity = options.getZipSecurity();
|
||||||
this.useLimitedDataStream = zipSecurity.useLimitedDataStream();
|
this.useLimitedDataStream = zipSecurity.useLimitedDataStream();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new FallbackException("Error opening zip file: " + file.getAbsolutePath(), e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ZipContent open() throws IOException {
|
public ZipContent open() throws IOException {
|
||||||
zipFile = new ZipFile(file);
|
try {
|
||||||
|
int maxEntriesCount = zipSecurity.getMaxEntriesCount();
|
||||||
int maxEntriesCount = zipSecurity.getMaxEntriesCount();
|
if (maxEntriesCount == -1) {
|
||||||
if (maxEntriesCount == -1) {
|
maxEntriesCount = Integer.MAX_VALUE;
|
||||||
maxEntriesCount = Integer.MAX_VALUE;
|
}
|
||||||
}
|
List<IZipEntry> list = new ArrayList<>();
|
||||||
|
Enumeration<? extends ZipEntry> entries = zipFile.entries();
|
||||||
List<IZipEntry> list = new ArrayList<>();
|
while (entries.hasMoreElements()) {
|
||||||
Enumeration<? extends ZipEntry> entries = zipFile.entries();
|
FallbackZipEntry zipEntry = new FallbackZipEntry(this, entries.nextElement());
|
||||||
while (entries.hasMoreElements()) {
|
if (isValidEntry(zipEntry)) {
|
||||||
FallbackZipEntry zipEntry = new FallbackZipEntry(this, entries.nextElement());
|
list.add(zipEntry);
|
||||||
if (isValidEntry(zipEntry)) {
|
if (list.size() > maxEntriesCount) {
|
||||||
list.add(zipEntry);
|
throw new IllegalStateException("Max entries count limit exceeded: " + list.size());
|
||||||
if (list.size() > maxEntriesCount) {
|
}
|
||||||
throw new IllegalStateException("Max entries count limit exceeded: " + list.size());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return new ZipContent(this, list);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new FallbackException("Error opening zip file: " + file.getAbsolutePath(), e);
|
||||||
}
|
}
|
||||||
return new ZipContent(this, list);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isValidEntry(IZipEntry zipEntry) {
|
private boolean isValidEntry(IZipEntry zipEntry) {
|
||||||
@@ -98,12 +104,8 @@ public class FallbackZipParser implements IZipParser {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() throws IOException {
|
public void close() throws IOException {
|
||||||
try {
|
if (zipFile != null) {
|
||||||
if (zipFile != null) {
|
zipFile.close();
|
||||||
zipFile.close();
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
zipFile = null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ public class ByteBufferBackedInputStream extends InputStream {
|
|||||||
this.buf = buf;
|
this.buf = buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public int read() throws IOException {
|
public int read() throws IOException {
|
||||||
if (!buf.hasRemaining()) {
|
if (!buf.hasRemaining()) {
|
||||||
return -1;
|
return -1;
|
||||||
@@ -19,6 +20,7 @@ public class ByteBufferBackedInputStream extends InputStream {
|
|||||||
return buf.get() & 0xFF;
|
return buf.get() & 0xFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
@SuppressWarnings("NullableProblems")
|
@SuppressWarnings("NullableProblems")
|
||||||
public int read(byte[] bytes, int off, int len) throws IOException {
|
public int read(byte[] bytes, int off, int len) throws IOException {
|
||||||
if (!buf.hasRemaining()) {
|
if (!buf.hasRemaining()) {
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ public final class JadxZipEntry implements IZipEntry {
|
|||||||
return compressedSize <= uncompressedSize;
|
return compressedSize <= uncompressedSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return fileName;
|
return fileName;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,9 +49,9 @@ public final class JadxZipParser implements IZipParser {
|
|||||||
private final boolean verify;
|
private final boolean verify;
|
||||||
private final boolean useLimitedDataStream;
|
private final boolean useLimitedDataStream;
|
||||||
|
|
||||||
private RandomAccessFile file;
|
private @Nullable RandomAccessFile file;
|
||||||
private FileChannel fileChannel;
|
private @Nullable FileChannel fileChannel;
|
||||||
private ByteBuffer byteBuffer;
|
private @Nullable ByteBuffer byteBuffer;
|
||||||
|
|
||||||
private int endOfCDStart = -2;
|
private int endOfCDStart = -2;
|
||||||
|
|
||||||
@@ -90,23 +90,25 @@ public final class JadxZipParser implements IZipParser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("RedundantIfStatement")
|
|
||||||
public boolean canOpen() {
|
public boolean canOpen() {
|
||||||
try {
|
try {
|
||||||
load();
|
load();
|
||||||
int eocdStart = searchEndOfCDStart();
|
int eocdStart = searchEndOfCDStart();
|
||||||
ByteBuffer buf = byteBuffer;
|
ByteBuffer buf = getBuffer();
|
||||||
buf.position(eocdStart + 4);
|
buf.position(eocdStart + 4);
|
||||||
int diskNum = readU2(buf);
|
int diskNum = readU2(buf);
|
||||||
if (diskNum == 0xFFFF) {
|
if (diskNum != 0xFFFF) { // Zip64 not supported
|
||||||
// Zip64
|
return true;
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOG.warn("Jadx parser can't open zip file: {}", zipFile, e);
|
LOG.warn("Jadx parser can't open zip file: {}", zipFile, e);
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
|
close();
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.warn("Failed to close jadx parser, zip file: {}", zipFile, e);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isValidEntry(JadxZipEntry zipEntry) {
|
private boolean isValidEntry(JadxZipEntry zipEntry) {
|
||||||
@@ -117,13 +119,21 @@ public final class JadxZipParser implements IZipParser {
|
|||||||
return validEntry;
|
return validEntry;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ByteBuffer getBuffer() {
|
||||||
|
ByteBuffer buf = byteBuffer;
|
||||||
|
if (buf == null) {
|
||||||
|
throw new RuntimeException("File not opened: " + zipFile);
|
||||||
|
}
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
private void load() throws IOException {
|
private void load() throws IOException {
|
||||||
if (byteBuffer != null) {
|
if (byteBuffer != null) {
|
||||||
// already loaded
|
// already loaded
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
file = new RandomAccessFile(zipFile, "r");
|
RandomAccessFile raFile = new RandomAccessFile(zipFile, "r");
|
||||||
long size = file.length();
|
long size = raFile.length();
|
||||||
if (size >= Integer.MAX_VALUE) {
|
if (size >= Integer.MAX_VALUE) {
|
||||||
throw new IOException("Zip file is too big");
|
throw new IOException("Zip file is too big");
|
||||||
}
|
}
|
||||||
@@ -131,16 +141,16 @@ public final class JadxZipParser implements IZipParser {
|
|||||||
if (fileLen < 100 * 1024 * 1024) {
|
if (fileLen < 100 * 1024 * 1024) {
|
||||||
// load files smaller than 100MB directly into memory
|
// load files smaller than 100MB directly into memory
|
||||||
byte[] bytes = new byte[fileLen];
|
byte[] bytes = new byte[fileLen];
|
||||||
file.readFully(bytes);
|
raFile.readFully(bytes);
|
||||||
byteBuffer = ByteBuffer.wrap(bytes).asReadOnlyBuffer();
|
byteBuffer = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN);
|
||||||
file.close();
|
raFile.close();
|
||||||
file = null;
|
|
||||||
} else {
|
} else {
|
||||||
// for big files - use a memory mapped file
|
// for big files - use a memory mapped file
|
||||||
fileChannel = file.getChannel();
|
file = raFile;
|
||||||
|
fileChannel = raFile.getChannel();
|
||||||
byteBuffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fileChannel.size());
|
byteBuffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fileChannel.size());
|
||||||
|
byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
}
|
}
|
||||||
byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<IZipEntry> searchLocalFileHeaders(int maxEntriesCount) {
|
private List<IZipEntry> searchLocalFileHeaders(int maxEntriesCount) {
|
||||||
@@ -165,7 +175,7 @@ public final class JadxZipParser implements IZipParser {
|
|||||||
if (eocdStart < 0) {
|
if (eocdStart < 0) {
|
||||||
throw new RuntimeException("End of central directory not found");
|
throw new RuntimeException("End of central directory not found");
|
||||||
}
|
}
|
||||||
ByteBuffer buf = byteBuffer;
|
ByteBuffer buf = getBuffer();
|
||||||
buf.position(eocdStart + 10);
|
buf.position(eocdStart + 10);
|
||||||
int entriesCount = readU2(buf);
|
int entriesCount = readU2(buf);
|
||||||
buf.position(eocdStart + 16);
|
buf.position(eocdStart + 16);
|
||||||
@@ -186,7 +196,7 @@ public final class JadxZipParser implements IZipParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private JadxZipEntry loadCDEntry() {
|
private JadxZipEntry loadCDEntry() {
|
||||||
ByteBuffer buf = byteBuffer;
|
ByteBuffer buf = getBuffer();
|
||||||
int start = buf.position();
|
int start = buf.position();
|
||||||
buf.position(start + 28);
|
buf.position(start + 28);
|
||||||
int fileNameLen = readU2(buf);
|
int fileNameLen = readU2(buf);
|
||||||
@@ -207,7 +217,7 @@ public final class JadxZipParser implements IZipParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private JadxZipEntry fixEntryFromCD(JadxZipEntry entry, int start) {
|
private JadxZipEntry fixEntryFromCD(JadxZipEntry entry, int start) {
|
||||||
ByteBuffer buf = byteBuffer;
|
ByteBuffer buf = getBuffer();
|
||||||
buf.position(start + 10);
|
buf.position(start + 10);
|
||||||
int comprMethod = readU2(buf);
|
int comprMethod = readU2(buf);
|
||||||
buf.position(start + 20);
|
buf.position(start + 20);
|
||||||
@@ -237,7 +247,7 @@ public final class JadxZipParser implements IZipParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private JadxZipEntry loadFileEntry(int start) {
|
private JadxZipEntry loadFileEntry(int start) {
|
||||||
ByteBuffer buf = byteBuffer;
|
ByteBuffer buf = getBuffer();
|
||||||
buf.position(start + 8);
|
buf.position(start + 8);
|
||||||
int comprMethod = readU2(buf);
|
int comprMethod = readU2(buf);
|
||||||
buf.position(start + 18);
|
buf.position(start + 18);
|
||||||
@@ -255,7 +265,7 @@ public final class JadxZipParser implements IZipParser {
|
|||||||
if (endOfCDStart != -2) {
|
if (endOfCDStart != -2) {
|
||||||
return endOfCDStart;
|
return endOfCDStart;
|
||||||
}
|
}
|
||||||
ByteBuffer buf = byteBuffer;
|
ByteBuffer buf = getBuffer();
|
||||||
int pos = buf.limit() - 22;
|
int pos = buf.limit() - 22;
|
||||||
int minPos = Math.max(0, pos - 0xffff);
|
int minPos = Math.max(0, pos - 0xffff);
|
||||||
while (true) {
|
while (true) {
|
||||||
@@ -273,7 +283,7 @@ public final class JadxZipParser implements IZipParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private int searchEntryStart() {
|
private int searchEntryStart() {
|
||||||
ByteBuffer buf = byteBuffer;
|
ByteBuffer buf = getBuffer();
|
||||||
while (true) {
|
while (true) {
|
||||||
int start = buf.position();
|
int start = buf.position();
|
||||||
if (start + 4 > buf.limit()) {
|
if (start + 4 > buf.limit()) {
|
||||||
@@ -297,14 +307,14 @@ public final class JadxZipParser implements IZipParser {
|
|||||||
InputStream stream;
|
InputStream stream;
|
||||||
if (entry.getCompressMethod() == 8) {
|
if (entry.getCompressMethod() == 8) {
|
||||||
try {
|
try {
|
||||||
stream = ZipDeflate.decompressEntryToStream(byteBuffer, entry);
|
stream = ZipDeflate.decompressEntryToStream(getBuffer(), entry);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
entryParseFailed(entry, e);
|
entryParseFailed(entry, e);
|
||||||
return useFallbackParser(entry).getInputStream();
|
return useFallbackParser(entry).getInputStream();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// treat any other compression methods values as UNCOMPRESSED
|
// treat any other compression methods values as UNCOMPRESSED
|
||||||
stream = bufferToStream(byteBuffer, entry.getDataStart(), (int) entry.getUncompressedSize());
|
stream = bufferToStream(getBuffer(), entry.getDataStart(), (int) entry.getUncompressedSize());
|
||||||
}
|
}
|
||||||
if (useLimitedDataStream) {
|
if (useLimitedDataStream) {
|
||||||
return new LimitedInputStream(stream, entry.getUncompressedSize());
|
return new LimitedInputStream(stream, entry.getUncompressedSize());
|
||||||
@@ -318,14 +328,14 @@ public final class JadxZipParser implements IZipParser {
|
|||||||
}
|
}
|
||||||
if (entry.getCompressMethod() == 8) {
|
if (entry.getCompressMethod() == 8) {
|
||||||
try {
|
try {
|
||||||
return ZipDeflate.decompressEntryToBytes(byteBuffer, entry);
|
return ZipDeflate.decompressEntryToBytes(getBuffer(), entry);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
entryParseFailed(entry, e);
|
entryParseFailed(entry, e);
|
||||||
return useFallbackParser(entry).getBytes();
|
return useFallbackParser(entry).getBytes();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// treat any other compression methods values as UNCOMPRESSED
|
// treat any other compression methods values as UNCOMPRESSED
|
||||||
return bufferToBytes(byteBuffer, entry.getDataStart(), (int) entry.getUncompressedSize());
|
return bufferToBytes(getBuffer(), entry.getDataStart(), (int) entry.getUncompressedSize());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void verifyEntry(JadxZipEntry entry) {
|
private static void verifyEntry(JadxZipEntry entry) {
|
||||||
@@ -361,7 +371,7 @@ public final class JadxZipParser implements IZipParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("resource")
|
@SuppressWarnings("resource")
|
||||||
private ZipContent initFallbackParser() {
|
private synchronized ZipContent initFallbackParser() {
|
||||||
if (fallbackZipContent == null) {
|
if (fallbackZipContent == null) {
|
||||||
try {
|
try {
|
||||||
fallbackZipContent = new FallbackZipParser(zipFile, options).open();
|
fallbackZipContent = new FallbackZipParser(zipFile, options).open();
|
||||||
@@ -378,7 +388,7 @@ public final class JadxZipParser implements IZipParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private int readFlags(JadxZipEntry entry) {
|
private int readFlags(JadxZipEntry entry) {
|
||||||
ByteBuffer buf = byteBuffer;
|
ByteBuffer buf = getBuffer();
|
||||||
buf.position(entry.getEntryStart() + 6);
|
buf.position(entry.getEntryStart() + 6);
|
||||||
return readU2(buf);
|
return readU2(buf);
|
||||||
}
|
}
|
||||||
@@ -407,6 +417,7 @@ public final class JadxZipParser implements IZipParser {
|
|||||||
return new String(bytes, StandardCharsets.UTF_8);
|
return new String(bytes, StandardCharsets.UTF_8);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("DataFlowIssue")
|
||||||
@Override
|
@Override
|
||||||
public void close() throws IOException {
|
public void close() throws IOException {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ public class JadxArgs implements Closeable {
|
|||||||
/**
|
/**
|
||||||
* Predicate that allows to filter the classes to be process based on their full name
|
* Predicate that allows to filter the classes to be process based on their full name
|
||||||
*/
|
*/
|
||||||
private Predicate<String> classFilter = null;
|
private @Nullable Predicate<String> classFilter = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save dependencies for classes accepted by {@code classFilter}
|
* Save dependencies for classes accepted by {@code classFilter}
|
||||||
@@ -227,7 +227,6 @@ public class JadxArgs implements Closeable {
|
|||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
try {
|
try {
|
||||||
inputFiles = null;
|
|
||||||
if (codeCache != null) {
|
if (codeCache != null) {
|
||||||
codeCache.close();
|
codeCache.close();
|
||||||
}
|
}
|
||||||
@@ -239,9 +238,6 @@ public class JadxArgs implements Closeable {
|
|||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOG.error("Failed to close JadxArgs", e);
|
LOG.error("Failed to close JadxArgs", e);
|
||||||
} finally {
|
|
||||||
codeCache = null;
|
|
||||||
usageInfoCache = null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,12 +6,11 @@ import java.util.Comparator;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
import org.jetbrains.annotations.ApiStatus;
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import jadx.api.metadata.ICodeAnnotation;
|
import jadx.api.metadata.ICodeAnnotation;
|
||||||
import jadx.api.metadata.ICodeNodeRef;
|
import jadx.api.metadata.ICodeNodeRef;
|
||||||
@@ -26,11 +25,9 @@ import jadx.core.dex.nodes.MethodNode;
|
|||||||
import jadx.core.utils.ListUtils;
|
import jadx.core.utils.ListUtils;
|
||||||
|
|
||||||
public final class JavaClass implements JavaNode {
|
public final class JavaClass implements JavaNode {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(JavaClass.class);
|
private final @Nullable JadxDecompiler decompiler;
|
||||||
|
|
||||||
private final JadxDecompiler decompiler;
|
|
||||||
private final ClassNode cls;
|
private final ClassNode cls;
|
||||||
private final JavaClass parent;
|
private final @Nullable JavaClass parent;
|
||||||
|
|
||||||
private List<JavaClass> innerClasses = Collections.emptyList();
|
private List<JavaClass> innerClasses = Collections.emptyList();
|
||||||
private List<JavaClass> inlinedClasses = Collections.emptyList();
|
private List<JavaClass> inlinedClasses = Collections.emptyList();
|
||||||
@@ -38,7 +35,7 @@ public final class JavaClass implements JavaNode {
|
|||||||
private List<JavaMethod> methods = Collections.emptyList();
|
private List<JavaMethod> methods = Collections.emptyList();
|
||||||
private boolean listsLoaded;
|
private boolean listsLoaded;
|
||||||
|
|
||||||
JavaClass(ClassNode classNode, JadxDecompiler decompiler) {
|
JavaClass(ClassNode classNode, @NotNull JadxDecompiler decompiler) {
|
||||||
this.decompiler = decompiler;
|
this.decompiler = decompiler;
|
||||||
this.cls = classNode;
|
this.cls = classNode;
|
||||||
this.parent = null;
|
this.parent = null;
|
||||||
@@ -47,7 +44,7 @@ public final class JavaClass implements JavaNode {
|
|||||||
/**
|
/**
|
||||||
* Inner classes constructor
|
* Inner classes constructor
|
||||||
*/
|
*/
|
||||||
JavaClass(ClassNode classNode, JavaClass parent) {
|
JavaClass(ClassNode classNode, @NotNull JavaClass parent) {
|
||||||
this.decompiler = null;
|
this.decompiler = null;
|
||||||
this.cls = classNode;
|
this.cls = classNode;
|
||||||
this.parent = parent;
|
this.parent = parent;
|
||||||
@@ -69,6 +66,21 @@ public final class JavaClass implements JavaNode {
|
|||||||
load();
|
load();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detect if calling load() would trigger a potentially expensive decompilation operation.
|
||||||
|
*/
|
||||||
|
public boolean loadingWouldRequireDecompilation() {
|
||||||
|
if (listsLoaded) {
|
||||||
|
// lists are already populated, so it's safe regardless of the state of the class itself
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (cls.getState().isProcessComplete()) {
|
||||||
|
// decompilation has already finished
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
public synchronized ICodeInfo reload() {
|
public synchronized ICodeInfo reload() {
|
||||||
listsLoaded = false;
|
listsLoaded = false;
|
||||||
return cls.reloadCode();
|
return cls.reloadCode();
|
||||||
@@ -187,10 +199,10 @@ public final class JavaClass implements JavaNode {
|
|||||||
if (parent != null) {
|
if (parent != null) {
|
||||||
return parent.getRootDecompiler();
|
return parent.getRootDecompiler();
|
||||||
}
|
}
|
||||||
return decompiler;
|
return Objects.requireNonNull(decompiler);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ICodeAnnotation getAnnotationAt(int pos) {
|
public @Nullable ICodeAnnotation getAnnotationAt(int pos) {
|
||||||
return getCodeInfo().getCodeMetadata().getAt(pos);
|
return getCodeInfo().getCodeMetadata().getAt(pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -259,7 +271,7 @@ public final class JavaClass implements JavaNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public JavaClass getDeclaringClass() {
|
public @Nullable JavaClass getDeclaringClass() {
|
||||||
return parent;
|
return parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -362,21 +374,4 @@ public final class JavaClass implements JavaNode {
|
|||||||
public String toString() {
|
public String toString() {
|
||||||
return getFullName();
|
return getFullName();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Detect if calling load() would trigger a potentially expensive decompilation operation.
|
|
||||||
*/
|
|
||||||
public boolean loadingWouldRequireDecompilation() {
|
|
||||||
if (listsLoaded) {
|
|
||||||
// lists are already poplulated, so it's safe regardless of the state of the class itself
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cls.getState().isProcessComplete()) {
|
|
||||||
// decompilation has already finished
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,6 @@ import java.util.List;
|
|||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.jetbrains.annotations.ApiStatus;
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import jadx.api.metadata.ICodeAnnotation;
|
import jadx.api.metadata.ICodeAnnotation;
|
||||||
import jadx.api.metadata.ICodeNodeRef;
|
import jadx.api.metadata.ICodeNodeRef;
|
||||||
@@ -19,8 +17,6 @@ import jadx.core.dex.nodes.MethodNode;
|
|||||||
import jadx.core.utils.Utils;
|
import jadx.core.utils.Utils;
|
||||||
|
|
||||||
public final class JavaMethod implements JavaNode {
|
public final class JavaMethod implements JavaNode {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(JavaMethod.class);
|
|
||||||
|
|
||||||
private final MethodNode mth;
|
private final MethodNode mth;
|
||||||
private final JavaClass parent;
|
private final JavaClass parent;
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import jadx.core.dex.nodes.ClassNode;
|
|||||||
import jadx.core.dex.nodes.FieldNode;
|
import jadx.core.dex.nodes.FieldNode;
|
||||||
import jadx.core.dex.nodes.IFieldInfoRef;
|
import jadx.core.dex.nodes.IFieldInfoRef;
|
||||||
import jadx.core.dex.nodes.RootNode;
|
import jadx.core.dex.nodes.RootNode;
|
||||||
import jadx.core.dex.visitors.prepare.CollectConstValues;
|
|
||||||
|
|
||||||
public class ConstStorage {
|
public class ConstStorage {
|
||||||
|
|
||||||
@@ -23,18 +22,18 @@ public class ConstStorage {
|
|||||||
private final Map<Object, IFieldInfoRef> values = new ConcurrentHashMap<>();
|
private final Map<Object, IFieldInfoRef> values = new ConcurrentHashMap<>();
|
||||||
private final Set<Object> duplicates = new HashSet<>();
|
private final Set<Object> duplicates = new HashSet<>();
|
||||||
|
|
||||||
public Map<Object, IFieldInfoRef> getValues() {
|
Map<Object, IFieldInfoRef> getValues() {
|
||||||
return values;
|
return values;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IFieldInfoRef get(Object key) {
|
IFieldInfoRef get(Object key) {
|
||||||
return values.get(key);
|
return values.get(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return true if this value is duplicated
|
* @return true if this value is duplicated
|
||||||
*/
|
*/
|
||||||
public boolean put(Object value, IFieldInfoRef fld) {
|
boolean put(Object value, IFieldInfoRef fld) {
|
||||||
if (duplicates.contains(value)) {
|
if (duplicates.contains(value)) {
|
||||||
values.remove(value);
|
values.remove(value);
|
||||||
return true;
|
return true;
|
||||||
@@ -85,14 +84,6 @@ public class ConstStorage {
|
|||||||
globalValues.put(value, fld);
|
globalValues.put(value, fld);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Use method from CollectConstValues class
|
|
||||||
*/
|
|
||||||
@Deprecated
|
|
||||||
public static @Nullable Object getFieldConstValue(FieldNode fld) {
|
|
||||||
return CollectConstValues.getFieldConstValue(fld);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void removeForClass(ClassNode cls) {
|
public void removeForClass(ClassNode cls) {
|
||||||
classes.remove(cls);
|
classes.remove(cls);
|
||||||
globalValues.removeForCls(cls);
|
globalValues.removeForCls(cls);
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import org.jetbrains.annotations.NotNull;
|
|||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.jetbrains.annotations.TestOnly;
|
import org.jetbrains.annotations.TestOnly;
|
||||||
|
|
||||||
|
import com.google.errorprone.annotations.Immutable;
|
||||||
|
|
||||||
import jadx.core.Consts;
|
import jadx.core.Consts;
|
||||||
import jadx.core.dex.info.ClassInfo;
|
import jadx.core.dex.info.ClassInfo;
|
||||||
import jadx.core.dex.nodes.RootNode;
|
import jadx.core.dex.nodes.RootNode;
|
||||||
@@ -18,6 +20,7 @@ import jadx.core.utils.ListUtils;
|
|||||||
import jadx.core.utils.Utils;
|
import jadx.core.utils.Utils;
|
||||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
|
|
||||||
|
@Immutable
|
||||||
public abstract class ArgType {
|
public abstract class ArgType {
|
||||||
public static final ArgType INT = primitive(PrimitiveType.INT);
|
public static final ArgType INT = primitive(PrimitiveType.INT);
|
||||||
public static final ArgType BOOLEAN = primitive(PrimitiveType.BOOLEAN);
|
public static final ArgType BOOLEAN = primitive(PrimitiveType.BOOLEAN);
|
||||||
@@ -200,7 +203,7 @@ public abstract class ArgType {
|
|||||||
private static final class PrimitiveArg extends KnownType {
|
private static final class PrimitiveArg extends KnownType {
|
||||||
private final PrimitiveType type;
|
private final PrimitiveType type;
|
||||||
|
|
||||||
public PrimitiveArg(PrimitiveType type) {
|
PrimitiveArg(PrimitiveType type) {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.hash = type.hashCode();
|
this.hash = type.hashCode();
|
||||||
}
|
}
|
||||||
@@ -229,7 +232,7 @@ public abstract class ArgType {
|
|||||||
private static class ObjectType extends KnownType {
|
private static class ObjectType extends KnownType {
|
||||||
protected final String objName;
|
protected final String objName;
|
||||||
|
|
||||||
public ObjectType(String obj) {
|
ObjectType(String obj) {
|
||||||
this.objName = obj;
|
this.objName = obj;
|
||||||
this.hash = objName.hashCode();
|
this.hash = objName.hashCode();
|
||||||
}
|
}
|
||||||
@@ -263,15 +266,15 @@ public abstract class ArgType {
|
|||||||
private static final class GenericType extends ObjectType {
|
private static final class GenericType extends ObjectType {
|
||||||
private List<ArgType> extendTypes;
|
private List<ArgType> extendTypes;
|
||||||
|
|
||||||
public GenericType(String obj) {
|
GenericType(String obj) {
|
||||||
this(obj, Collections.emptyList());
|
this(obj, Collections.emptyList());
|
||||||
}
|
}
|
||||||
|
|
||||||
public GenericType(String obj, ArgType extendType) {
|
GenericType(String obj, ArgType extendType) {
|
||||||
this(obj, Collections.singletonList(extendType));
|
this(obj, Collections.singletonList(extendType));
|
||||||
}
|
}
|
||||||
|
|
||||||
public GenericType(String obj, List<ArgType> extendTypes) {
|
GenericType(String obj, List<ArgType> extendTypes) {
|
||||||
super(obj);
|
super(obj);
|
||||||
this.extendTypes = extendTypes;
|
this.extendTypes = extendTypes;
|
||||||
}
|
}
|
||||||
@@ -337,7 +340,7 @@ public abstract class ArgType {
|
|||||||
private final ArgType type;
|
private final ArgType type;
|
||||||
private final WildcardBound bound;
|
private final WildcardBound bound;
|
||||||
|
|
||||||
public WildcardType(ArgType obj, WildcardBound bound) {
|
WildcardType(ArgType obj, WildcardBound bound) {
|
||||||
super(OBJECT.getObject());
|
super(OBJECT.getObject());
|
||||||
this.type = Objects.requireNonNull(obj);
|
this.type = Objects.requireNonNull(obj);
|
||||||
this.bound = Objects.requireNonNull(bound);
|
this.bound = Objects.requireNonNull(bound);
|
||||||
@@ -382,7 +385,7 @@ public abstract class ArgType {
|
|||||||
private static class GenericObject extends ObjectType {
|
private static class GenericObject extends ObjectType {
|
||||||
private final List<ArgType> generics;
|
private final List<ArgType> generics;
|
||||||
|
|
||||||
public GenericObject(String obj, List<ArgType> generics) {
|
GenericObject(String obj, List<ArgType> generics) {
|
||||||
super(obj);
|
super(obj);
|
||||||
this.generics = Objects.requireNonNull(generics);
|
this.generics = Objects.requireNonNull(generics);
|
||||||
this.hash = calcHash();
|
this.hash = calcHash();
|
||||||
@@ -418,7 +421,7 @@ public abstract class ArgType {
|
|||||||
private final ObjectType outerType;
|
private final ObjectType outerType;
|
||||||
private final ObjectType innerType;
|
private final ObjectType innerType;
|
||||||
|
|
||||||
public OuterGenericObject(ObjectType outerType, ObjectType innerType) {
|
OuterGenericObject(ObjectType outerType, ObjectType innerType) {
|
||||||
super(outerType.getObject() + '$' + innerType.getObject());
|
super(outerType.getObject() + '$' + innerType.getObject());
|
||||||
this.outerType = outerType;
|
this.outerType = outerType;
|
||||||
this.innerType = innerType;
|
this.innerType = innerType;
|
||||||
@@ -466,7 +469,7 @@ public abstract class ArgType {
|
|||||||
private static final PrimitiveType[] ARRAY_POSSIBLES = new PrimitiveType[] { PrimitiveType.ARRAY };
|
private static final PrimitiveType[] ARRAY_POSSIBLES = new PrimitiveType[] { PrimitiveType.ARRAY };
|
||||||
private final ArgType arrayElement;
|
private final ArgType arrayElement;
|
||||||
|
|
||||||
public ArrayArg(ArgType arrayElement) {
|
ArrayArg(ArgType arrayElement) {
|
||||||
this.arrayElement = arrayElement;
|
this.arrayElement = arrayElement;
|
||||||
this.hash = arrayElement.hashCode();
|
this.hash = arrayElement.hashCode();
|
||||||
}
|
}
|
||||||
@@ -526,7 +529,7 @@ public abstract class ArgType {
|
|||||||
private static final class UnknownArg extends ArgType {
|
private static final class UnknownArg extends ArgType {
|
||||||
private final PrimitiveType[] possibleTypes;
|
private final PrimitiveType[] possibleTypes;
|
||||||
|
|
||||||
public UnknownArg(PrimitiveType[] types) {
|
UnknownArg(PrimitiveType[] types) {
|
||||||
this.possibleTypes = types;
|
this.possibleTypes = types;
|
||||||
this.hash = Arrays.hashCode(possibleTypes);
|
this.hash = Arrays.hashCode(possibleTypes);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
|
|||||||
// decompilation data, reset on unload
|
// decompilation data, reset on unload
|
||||||
private RegisterArg thisArg;
|
private RegisterArg thisArg;
|
||||||
private List<RegisterArg> argsList;
|
private List<RegisterArg> argsList;
|
||||||
private InsnNode[] instructions;
|
private @Nullable InsnNode[] instructions;
|
||||||
private List<BlockNode> blocks;
|
private List<BlockNode> blocks;
|
||||||
private int blocksMaxCId;
|
private int blocksMaxCId;
|
||||||
private BlockNode enterBlock;
|
private BlockNode enterBlock;
|
||||||
@@ -106,11 +106,12 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
|
|||||||
this.parentClass = classNode;
|
this.parentClass = classNode;
|
||||||
this.accFlags = new AccessInfo(mthData.getAccessFlags(), AFType.METHOD);
|
this.accFlags = new AccessInfo(mthData.getAccessFlags(), AFType.METHOD);
|
||||||
ICodeReader codeReader = mthData.getCodeReader();
|
ICodeReader codeReader = mthData.getCodeReader();
|
||||||
this.noCode = codeReader == null;
|
if (codeReader == null) {
|
||||||
if (noCode) {
|
this.noCode = true;
|
||||||
this.codeReader = null;
|
this.codeReader = null;
|
||||||
this.insnsCount = 0;
|
this.insnsCount = 0;
|
||||||
} else {
|
} else {
|
||||||
|
this.noCode = false;
|
||||||
this.codeReader = codeReader.copy();
|
this.codeReader = codeReader.copy();
|
||||||
this.insnsCount = codeReader.getUnitsCount();
|
this.insnsCount = codeReader.getUnitsCount();
|
||||||
}
|
}
|
||||||
@@ -713,6 +714,7 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Cannot modify through get, use setUseIn
|
// Cannot modify through get, use setUseIn
|
||||||
|
@Override
|
||||||
public List<MethodNode> getUseIn() {
|
public List<MethodNode> getUseIn() {
|
||||||
return Collections.unmodifiableList(useIn);
|
return Collections.unmodifiableList(useIn);
|
||||||
}
|
}
|
||||||
@@ -721,7 +723,7 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
|
|||||||
public void setUseIn(List<MethodNode> useIn) {
|
public void setUseIn(List<MethodNode> useIn) {
|
||||||
this.useIn = useIn;
|
this.useIn = useIn;
|
||||||
|
|
||||||
// Notify all methods (callers) this method (calee) is used in
|
// Notify all methods (callers) this method (callee) is used in
|
||||||
for (MethodNode methodUsedIn : useIn) {
|
for (MethodNode methodUsedIn : useIn) {
|
||||||
methodUsedIn.addUsed(this);
|
methodUsedIn.addUsed(this);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -209,7 +209,7 @@ public class StringUtils {
|
|||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String escapeXmlChar(char c) {
|
private static @Nullable String escapeXmlChar(char c) {
|
||||||
if (c <= 0x1F) {
|
if (c <= 0x1F) {
|
||||||
return "\\" + (int) c;
|
return "\\" + (int) c;
|
||||||
}
|
}
|
||||||
@@ -231,7 +231,7 @@ public class StringUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String escapeWhiteSpaceChar(char c) {
|
private static @Nullable String escapeWhiteSpaceChar(char c) {
|
||||||
switch (c) {
|
switch (c) {
|
||||||
case '\n':
|
case '\n':
|
||||||
return "\\n";
|
return "\\n";
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package jadx.core.utils;
|
|||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
import java.io.StringWriter;
|
import java.io.StringWriter;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.ArrayDeque;
|
import java.util.ArrayDeque;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
@@ -295,7 +296,7 @@ public class Utils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
try (PrintWriter pw = new PrintWriter(w, true)) {
|
try (PrintWriter pw = new PrintWriter(w, true, StandardCharsets.UTF_8)) {
|
||||||
filterRecursive(throwable);
|
filterRecursive(throwable);
|
||||||
throwable.printStackTrace(pw);
|
throwable.printStackTrace(pw);
|
||||||
pw.flush();
|
pw.flush();
|
||||||
@@ -618,7 +619,7 @@ public class Utils {
|
|||||||
private final AtomicInteger number = new AtomicInteger(0);
|
private final AtomicInteger number = new AtomicInteger(0);
|
||||||
private final String name;
|
private final String name;
|
||||||
|
|
||||||
public SimpleThreadFactory(String name) {
|
SimpleThreadFactory(String name) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+4
@@ -1,11 +1,15 @@
|
|||||||
package jadx.api.plugins.input.data;
|
package jadx.api.plugins.input.data;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
public interface IMethodHandle {
|
public interface IMethodHandle {
|
||||||
|
|
||||||
MethodHandleType getType();
|
MethodHandleType getType();
|
||||||
|
|
||||||
|
@Nullable
|
||||||
IFieldRef getFieldRef();
|
IFieldRef getFieldRef();
|
||||||
|
|
||||||
|
@Nullable
|
||||||
IMethodRef getMethodRef();
|
IMethodRef getMethodRef();
|
||||||
|
|
||||||
void load();
|
void load();
|
||||||
|
|||||||
+1
-1
@@ -8,7 +8,7 @@ import jadx.api.plugins.input.data.attributes.JadxAttrType;
|
|||||||
import jadx.api.plugins.input.data.attributes.PinnedAttribute;
|
import jadx.api.plugins.input.data.attributes.PinnedAttribute;
|
||||||
|
|
||||||
public class EncodedValue extends PinnedAttribute {
|
public class EncodedValue extends PinnedAttribute {
|
||||||
public static final EncodedValue NULL = new EncodedValue(EncodedType.ENCODED_NULL, null);
|
public static final EncodedValue NULL = new EncodedValue(EncodedType.ENCODED_NULL, "null");
|
||||||
|
|
||||||
private final EncodedType type;
|
private final EncodedType type;
|
||||||
private final Object value;
|
private final Object value;
|
||||||
|
|||||||
+1
-2
@@ -11,8 +11,7 @@ public interface IAnnotation {
|
|||||||
|
|
||||||
Map<String, EncodedValue> getValues();
|
Map<String, EncodedValue> getValues();
|
||||||
|
|
||||||
@Nullable
|
default @Nullable EncodedValue getDefaultValue() {
|
||||||
default EncodedValue getDefaultValue() {
|
|
||||||
return getValues().get("value");
|
return getValues().get("value");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-2
@@ -11,8 +11,7 @@ import jadx.api.plugins.input.data.attributes.PinnedAttribute;
|
|||||||
|
|
||||||
public class AnnotationMethodParamsAttr extends PinnedAttribute {
|
public class AnnotationMethodParamsAttr extends PinnedAttribute {
|
||||||
|
|
||||||
@Nullable
|
public static @Nullable AnnotationMethodParamsAttr pack(List<List<IAnnotation>> annotationRefList) {
|
||||||
public static AnnotationMethodParamsAttr pack(List<List<IAnnotation>> annotationRefList) {
|
|
||||||
if (annotationRefList.isEmpty()) {
|
if (annotationRefList.isEmpty()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-3
@@ -16,8 +16,7 @@ import jadx.api.plugins.input.data.attributes.PinnedAttribute;
|
|||||||
|
|
||||||
public class AnnotationsAttr extends PinnedAttribute {
|
public class AnnotationsAttr extends PinnedAttribute {
|
||||||
|
|
||||||
@Nullable
|
public static @Nullable AnnotationsAttr pack(List<IAnnotation> annotationList) {
|
||||||
public static AnnotationsAttr pack(List<IAnnotation> annotationList) {
|
|
||||||
if (annotationList.isEmpty()) {
|
if (annotationList.isEmpty()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -39,7 +38,7 @@ public class AnnotationsAttr extends PinnedAttribute {
|
|||||||
this.map = map;
|
this.map = map;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IAnnotation get(String className) {
|
public @Nullable IAnnotation get(String className) {
|
||||||
return map.get(className);
|
return map.get(className);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
@@ -27,6 +27,7 @@ public class MethodParametersAttr extends PinnedAttribute {
|
|||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return AccessFlags.format(accFlags, AccessFlagsScope.METHOD) + name;
|
return AccessFlags.format(accFlags, AccessFlagsScope.METHOD) + name;
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-2
@@ -1,5 +1,7 @@
|
|||||||
package jadx.api.plugins.input.data.impl;
|
package jadx.api.plugins.input.data.impl;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import jadx.api.plugins.input.data.IFieldRef;
|
import jadx.api.plugins.input.data.IFieldRef;
|
||||||
import jadx.api.plugins.input.data.IMethodHandle;
|
import jadx.api.plugins.input.data.IMethodHandle;
|
||||||
import jadx.api.plugins.input.data.IMethodRef;
|
import jadx.api.plugins.input.data.IMethodRef;
|
||||||
@@ -21,12 +23,12 @@ public class FieldRefHandle implements IMethodHandle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IFieldRef getFieldRef() {
|
public @Nullable IFieldRef getFieldRef() {
|
||||||
return fieldRef;
|
return fieldRef;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IMethodRef getMethodRef() {
|
public @Nullable IMethodRef getMethodRef() {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-5
@@ -9,7 +9,7 @@ import jadx.api.plugins.input.data.ISeqConsumer;
|
|||||||
|
|
||||||
public class ListConsumer<T, R> implements ISeqConsumer<T> {
|
public class ListConsumer<T, R> implements ISeqConsumer<T> {
|
||||||
private final Function<T, R> convert;
|
private final Function<T, R> convert;
|
||||||
private List<R> list;
|
private List<R> list = new ArrayList<>();
|
||||||
|
|
||||||
public ListConsumer(Function<T, R> convert) {
|
public ListConsumer(Function<T, R> convert) {
|
||||||
this.convert = convert;
|
this.convert = convert;
|
||||||
@@ -26,10 +26,6 @@ public class ListConsumer<T, R> implements ISeqConsumer<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public List<R> getResult() {
|
public List<R> getResult() {
|
||||||
if (list == null) {
|
|
||||||
// init not called
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-2
@@ -1,5 +1,7 @@
|
|||||||
package jadx.api.plugins.input.data.impl;
|
package jadx.api.plugins.input.data.impl;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import jadx.api.plugins.input.data.IFieldData;
|
import jadx.api.plugins.input.data.IFieldData;
|
||||||
import jadx.api.plugins.input.data.IMethodHandle;
|
import jadx.api.plugins.input.data.IMethodHandle;
|
||||||
import jadx.api.plugins.input.data.IMethodRef;
|
import jadx.api.plugins.input.data.IMethodRef;
|
||||||
@@ -21,12 +23,12 @@ public class MethodRefHandle implements IMethodHandle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IMethodRef getMethodRef() {
|
public @Nullable IMethodRef getMethodRef() {
|
||||||
return methodRef;
|
return methodRef;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IFieldData getFieldRef() {
|
public @Nullable IFieldData getFieldRef() {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user