diff --git a/.gitignore b/.gitignore index bb51a528f..122e32c24 100644 --- a/.gitignore +++ b/.gitignore @@ -39,5 +39,7 @@ jadx-output/ *.orig quark.json +.env + cliff.toml jadx-gui/src/main/resources/logback.xml diff --git a/build.gradle.kts b/build.gradle.kts index 5dee185b8..7b48cf5ae 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -11,7 +11,9 @@ plugins { 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") version = jadxVersion @@ -19,7 +21,7 @@ val jadxBuildJavaVersion by extra { getBuildJavaVersion() } fun getBuildJavaVersion(): Int? { val envVarName = "JADX_BUILD_JAVA_VERSION" - val buildJavaVer = System.getenv(envVarName)?.toInt() ?: return null + val buildJavaVer = jadxEnv[envVarName]?.toInt() ?: return null if (buildJavaVer < 11) { throw GradleException("'$envVarName' can't be set to lower than 11") } @@ -27,6 +29,24 @@ fun getBuildJavaVersion(): Int? { 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 { apply(plugin = "java") apply(plugin = "checkstyle") @@ -82,6 +102,30 @@ fun isNonStable(version: String): Boolean { return isStable.not() } +fun loadEnv(file: File): Map { + val envMap = HashMap() + 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 { isCanBeConsumed = false } diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index ed67e57a2..15f610b40 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -6,6 +6,8 @@ dependencies { implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:2.3.10") 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 { diff --git a/buildSrc/src/main/kotlin/jadx-java.gradle.kts b/buildSrc/src/main/kotlin/jadx-java.gradle.kts index c44819d11..caf20ac14 100644 --- a/buildSrc/src/main/kotlin/jadx-java.gradle.kts +++ b/buildSrc/src/main/kotlin/jadx-java.gradle.kts @@ -1,14 +1,20 @@ 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 { java checkstyle id("jadx-rewrite") + id("net.ltgt.errorprone") + id("net.ltgt.nullaway") } val jadxVersion: String by rootProject.extra val jadxBuildJavaVersion: Int? by rootProject.extra +val jadxBuildChecksMode: String by rootProject.extra group = "io.github.skylot" version = jadxVersion @@ -24,6 +30,9 @@ dependencies { testRuntimeOnly("org.junit.platform:junit-platform-launcher") 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 { @@ -62,3 +71,27 @@ tasks { } } } + +tasks.withType().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") + } +} diff --git a/jadx-commons/jadx-app-commons/src/main/java/jadx/commons/app/JadxCommonEnv.java b/jadx-commons/jadx-app-commons/src/main/java/jadx/commons/app/JadxCommonEnv.java index cbe5fbbd3..d985f7dea 100644 --- a/jadx-commons/jadx-app-commons/src/main/java/jadx/commons/app/JadxCommonEnv.java +++ b/jadx-commons/jadx-app-commons/src/main/java/jadx/commons/app/JadxCommonEnv.java @@ -1,8 +1,10 @@ package jadx.commons.app; +import org.jetbrains.annotations.Nullable; + 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); return isNullOrEmpty(strValue) ? defValue : strValue; } @@ -23,7 +25,7 @@ public class JadxCommonEnv { return Integer.parseInt(strValue); } - private static boolean isNullOrEmpty(String value) { + private static boolean isNullOrEmpty(@Nullable String value) { return value == null || value.isEmpty(); } } diff --git a/jadx-commons/jadx-app-commons/src/main/java/jadx/commons/app/JadxCommonFiles.java b/jadx-commons/jadx-app-commons/src/main/java/jadx/commons/app/JadxCommonFiles.java index d600b2c3c..68ce2ddd4 100644 --- a/jadx-commons/jadx-app-commons/src/main/java/jadx/commons/app/JadxCommonFiles.java +++ b/jadx-commons/jadx-app-commons/src/main/java/jadx/commons/app/JadxCommonFiles.java @@ -3,7 +3,8 @@ package jadx.commons.app; import java.io.IOException; import java.nio.file.Files; 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.slf4j.Logger; @@ -30,40 +31,39 @@ public class JadxCommonFiles { 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; + private final Path configDir; + private final Path cacheDir; - public void init() { + DirsLoader() { try { - configDir = loadEnvDir("JADX_CONFIG_DIR", pd -> pd.configDir); - cacheDir = loadEnvDir("JADX_CACHE_DIR", pd -> pd.cacheDir); + AtomicReference<@Nullable ProjectDirectories> pdRef = new AtomicReference<>(); + configDir = loadEnvDir("JADX_CONFIG_DIR", () -> loadDirs(pdRef).configDir); + cacheDir = loadEnvDir("JADX_CACHE_DIR", () -> loadDirs(pdRef).cacheDir); } catch (Exception e) { throw new RuntimeException("Failed to init common directories", e); } } - private Path loadEnvDir(String envVar, Function dirFunc) throws IOException { + private static Path loadEnvDir(String envVar, Supplier dirFunc) throws IOException { String envDir = JadxCommonEnv.get(envVar, null); String dirStr; if (envDir != null) { dirStr = envDir; } else { - dirStr = dirFunc.apply(loadDirs()); + dirStr = dirFunc.get(); } Path path = Path.of(dirStr).toAbsolutePath(); Files.createDirectories(path); return path; } - private synchronized ProjectDirectories loadDirs() { - ProjectDirectories currentDirs = dirs; + private static ProjectDirectories loadDirs(AtomicReference<@Nullable ProjectDirectories> pdRef) { + ProjectDirectories currentDirs = pdRef.get(); if (currentDirs != null) { return currentDirs; } @@ -76,7 +76,7 @@ public class JadxCommonFiles { LOG.debug("Loaded system dirs ({}ms): config: {}, cache: {}", System.currentTimeMillis() - start, loadedDirs.configDir, loadedDirs.cacheDir); } - dirs = loadedDirs; + pdRef.set(loadedDirs); return loadedDirs; } @@ -95,11 +95,11 @@ public class JadxCommonFiles { return impl; } - public Path getCacheDir() { + Path getCacheDir() { return cacheDir; } - public Path getConfigDir() { + Path getConfigDir() { return configDir; } } diff --git a/jadx-commons/jadx-zip/src/main/java/jadx/zip/ZipReader.java b/jadx-commons/jadx-zip/src/main/java/jadx/zip/ZipReader.java index a9cf6fbd6..8452521b0 100644 --- a/jadx-commons/jadx-zip/src/main/java/jadx/zip/ZipReader.java +++ b/jadx-commons/jadx-zip/src/main/java/jadx/zip/ZipReader.java @@ -1,6 +1,7 @@ package jadx.zip; import java.io.File; +import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.util.Set; @@ -9,6 +10,7 @@ import java.util.function.Function; import org.jetbrains.annotations.Nullable; +import jadx.zip.fallback.FallbackException; import jadx.zip.fallback.FallbackZipParser; import jadx.zip.parser.JadxZipParser; import jadx.zip.security.IJadxZipSecurity; @@ -39,13 +41,15 @@ public class ZipReader { @SuppressWarnings("resource") public ZipContent open(File zipFile) throws IOException { + if (!zipFile.exists()) { + throw new FileNotFoundException(zipFile.getAbsolutePath()); + } try { JadxZipParser jadxParser = new JadxZipParser(zipFile, options); IZipParser detectedParser = detectParser(zipFile, jadxParser); - if (detectedParser != jadxParser) { - jadxParser.close(); - } return detectedParser.open(); + } catch (FallbackException e) { + throw e; } catch (Exception e) { if (options.getFlags().contains(ZipReaderFlags.DONT_USE_FALLBACK)) { throw new IOException("Failed to open zip: " + zipFile, e); @@ -90,7 +94,7 @@ public class ZipReader { return options; } - private IZipParser detectParser(File zipFile, JadxZipParser jadxParser) { + private IZipParser detectParser(File zipFile, JadxZipParser jadxParser) throws IOException { if (zipFile.getName().endsWith(".apk") || options.getFlags().contains(ZipReaderFlags.DONT_USE_FALLBACK)) { return jadxParser; @@ -105,7 +109,7 @@ public class ZipReader { return jadxParser; } - private FallbackZipParser buildFallbackParser(File zipFile) { + private FallbackZipParser buildFallbackParser(File zipFile) throws IOException { return new FallbackZipParser(zipFile, options); } } diff --git a/jadx-commons/jadx-zip/src/main/java/jadx/zip/fallback/FallbackException.java b/jadx-commons/jadx-zip/src/main/java/jadx/zip/fallback/FallbackException.java new file mode 100644 index 000000000..865f287f4 --- /dev/null +++ b/jadx-commons/jadx-zip/src/main/java/jadx/zip/fallback/FallbackException.java @@ -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); + } +} diff --git a/jadx-commons/jadx-zip/src/main/java/jadx/zip/fallback/FallbackZipParser.java b/jadx-commons/jadx-zip/src/main/java/jadx/zip/fallback/FallbackZipParser.java index 7e41fd224..77626976f 100644 --- a/jadx-commons/jadx-zip/src/main/java/jadx/zip/fallback/FallbackZipParser.java +++ b/jadx-commons/jadx-zip/src/main/java/jadx/zip/fallback/FallbackZipParser.java @@ -22,39 +22,45 @@ import jadx.zip.security.IJadxZipSecurity; public class FallbackZipParser implements IZipParser { private static final Logger LOG = LoggerFactory.getLogger(FallbackZipParser.class); + private final File file; + private final ZipFile zipFile; private final IJadxZipSecurity zipSecurity; private final boolean useLimitedDataStream; - private ZipFile zipFile; - - public FallbackZipParser(File file, ZipReaderOptions options) { - this.file = file; - this.zipSecurity = options.getZipSecurity(); - this.useLimitedDataStream = zipSecurity.useLimitedDataStream(); + public FallbackZipParser(File file, ZipReaderOptions options) throws FallbackException { + try { + this.file = file; + this.zipFile = new ZipFile(file); + this.zipSecurity = options.getZipSecurity(); + this.useLimitedDataStream = zipSecurity.useLimitedDataStream(); + } catch (Exception e) { + throw new FallbackException("Error opening zip file: " + file.getAbsolutePath(), e); + } } @Override public ZipContent open() throws IOException { - zipFile = new ZipFile(file); - - int maxEntriesCount = zipSecurity.getMaxEntriesCount(); - if (maxEntriesCount == -1) { - maxEntriesCount = Integer.MAX_VALUE; - } - - List list = new ArrayList<>(); - Enumeration entries = zipFile.entries(); - while (entries.hasMoreElements()) { - FallbackZipEntry zipEntry = new FallbackZipEntry(this, entries.nextElement()); - if (isValidEntry(zipEntry)) { - list.add(zipEntry); - if (list.size() > maxEntriesCount) { - throw new IllegalStateException("Max entries count limit exceeded: " + list.size()); + try { + int maxEntriesCount = zipSecurity.getMaxEntriesCount(); + if (maxEntriesCount == -1) { + maxEntriesCount = Integer.MAX_VALUE; + } + List list = new ArrayList<>(); + Enumeration entries = zipFile.entries(); + while (entries.hasMoreElements()) { + FallbackZipEntry zipEntry = new FallbackZipEntry(this, entries.nextElement()); + if (isValidEntry(zipEntry)) { + list.add(zipEntry); + 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) { @@ -98,12 +104,8 @@ public class FallbackZipParser implements IZipParser { @Override public void close() throws IOException { - try { - if (zipFile != null) { - zipFile.close(); - } - } finally { - zipFile = null; + if (zipFile != null) { + zipFile.close(); } } } diff --git a/jadx-commons/jadx-zip/src/main/java/jadx/zip/io/ByteBufferBackedInputStream.java b/jadx-commons/jadx-zip/src/main/java/jadx/zip/io/ByteBufferBackedInputStream.java index 8b54d80dd..1e171e469 100644 --- a/jadx-commons/jadx-zip/src/main/java/jadx/zip/io/ByteBufferBackedInputStream.java +++ b/jadx-commons/jadx-zip/src/main/java/jadx/zip/io/ByteBufferBackedInputStream.java @@ -12,6 +12,7 @@ public class ByteBufferBackedInputStream extends InputStream { this.buf = buf; } + @Override public int read() throws IOException { if (!buf.hasRemaining()) { return -1; @@ -19,6 +20,7 @@ public class ByteBufferBackedInputStream extends InputStream { return buf.get() & 0xFF; } + @Override @SuppressWarnings("NullableProblems") public int read(byte[] bytes, int off, int len) throws IOException { if (!buf.hasRemaining()) { diff --git a/jadx-commons/jadx-zip/src/main/java/jadx/zip/parser/JadxZipEntry.java b/jadx-commons/jadx-zip/src/main/java/jadx/zip/parser/JadxZipEntry.java index d019b4411..9d6405dad 100644 --- a/jadx-commons/jadx-zip/src/main/java/jadx/zip/parser/JadxZipEntry.java +++ b/jadx-commons/jadx-zip/src/main/java/jadx/zip/parser/JadxZipEntry.java @@ -35,6 +35,7 @@ public final class JadxZipEntry implements IZipEntry { return compressedSize <= uncompressedSize; } + @Override public String getName() { return fileName; } diff --git a/jadx-commons/jadx-zip/src/main/java/jadx/zip/parser/JadxZipParser.java b/jadx-commons/jadx-zip/src/main/java/jadx/zip/parser/JadxZipParser.java index 2d74ef7c2..2d40ee9d9 100644 --- a/jadx-commons/jadx-zip/src/main/java/jadx/zip/parser/JadxZipParser.java +++ b/jadx-commons/jadx-zip/src/main/java/jadx/zip/parser/JadxZipParser.java @@ -49,9 +49,9 @@ public final class JadxZipParser implements IZipParser { private final boolean verify; private final boolean useLimitedDataStream; - private RandomAccessFile file; - private FileChannel fileChannel; - private ByteBuffer byteBuffer; + private @Nullable RandomAccessFile file; + private @Nullable FileChannel fileChannel; + private @Nullable ByteBuffer byteBuffer; private int endOfCDStart = -2; @@ -90,23 +90,25 @@ public final class JadxZipParser implements IZipParser { } } - @SuppressWarnings("RedundantIfStatement") public boolean canOpen() { try { load(); int eocdStart = searchEndOfCDStart(); - ByteBuffer buf = byteBuffer; + ByteBuffer buf = getBuffer(); buf.position(eocdStart + 4); int diskNum = readU2(buf); - if (diskNum == 0xFFFF) { - // Zip64 - return false; + if (diskNum != 0xFFFF) { // Zip64 not supported + return true; } - return true; } catch (Exception 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) { @@ -117,13 +119,21 @@ public final class JadxZipParser implements IZipParser { 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 { if (byteBuffer != null) { // already loaded return; } - file = new RandomAccessFile(zipFile, "r"); - long size = file.length(); + RandomAccessFile raFile = new RandomAccessFile(zipFile, "r"); + long size = raFile.length(); if (size >= Integer.MAX_VALUE) { throw new IOException("Zip file is too big"); } @@ -131,16 +141,16 @@ public final class JadxZipParser implements IZipParser { if (fileLen < 100 * 1024 * 1024) { // load files smaller than 100MB directly into memory byte[] bytes = new byte[fileLen]; - file.readFully(bytes); - byteBuffer = ByteBuffer.wrap(bytes).asReadOnlyBuffer(); - file.close(); - file = null; + raFile.readFully(bytes); + byteBuffer = ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN); + raFile.close(); } else { // 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.order(ByteOrder.LITTLE_ENDIAN); } - byteBuffer.order(ByteOrder.LITTLE_ENDIAN); } private List searchLocalFileHeaders(int maxEntriesCount) { @@ -165,7 +175,7 @@ public final class JadxZipParser implements IZipParser { if (eocdStart < 0) { throw new RuntimeException("End of central directory not found"); } - ByteBuffer buf = byteBuffer; + ByteBuffer buf = getBuffer(); buf.position(eocdStart + 10); int entriesCount = readU2(buf); buf.position(eocdStart + 16); @@ -186,7 +196,7 @@ public final class JadxZipParser implements IZipParser { } private JadxZipEntry loadCDEntry() { - ByteBuffer buf = byteBuffer; + ByteBuffer buf = getBuffer(); int start = buf.position(); buf.position(start + 28); int fileNameLen = readU2(buf); @@ -207,7 +217,7 @@ public final class JadxZipParser implements IZipParser { } private JadxZipEntry fixEntryFromCD(JadxZipEntry entry, int start) { - ByteBuffer buf = byteBuffer; + ByteBuffer buf = getBuffer(); buf.position(start + 10); int comprMethod = readU2(buf); buf.position(start + 20); @@ -237,7 +247,7 @@ public final class JadxZipParser implements IZipParser { } private JadxZipEntry loadFileEntry(int start) { - ByteBuffer buf = byteBuffer; + ByteBuffer buf = getBuffer(); buf.position(start + 8); int comprMethod = readU2(buf); buf.position(start + 18); @@ -255,7 +265,7 @@ public final class JadxZipParser implements IZipParser { if (endOfCDStart != -2) { return endOfCDStart; } - ByteBuffer buf = byteBuffer; + ByteBuffer buf = getBuffer(); int pos = buf.limit() - 22; int minPos = Math.max(0, pos - 0xffff); while (true) { @@ -273,7 +283,7 @@ public final class JadxZipParser implements IZipParser { } private int searchEntryStart() { - ByteBuffer buf = byteBuffer; + ByteBuffer buf = getBuffer(); while (true) { int start = buf.position(); if (start + 4 > buf.limit()) { @@ -297,14 +307,14 @@ public final class JadxZipParser implements IZipParser { InputStream stream; if (entry.getCompressMethod() == 8) { try { - stream = ZipDeflate.decompressEntryToStream(byteBuffer, entry); + stream = ZipDeflate.decompressEntryToStream(getBuffer(), entry); } catch (Exception e) { entryParseFailed(entry, e); return useFallbackParser(entry).getInputStream(); } } else { // 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) { return new LimitedInputStream(stream, entry.getUncompressedSize()); @@ -318,14 +328,14 @@ public final class JadxZipParser implements IZipParser { } if (entry.getCompressMethod() == 8) { try { - return ZipDeflate.decompressEntryToBytes(byteBuffer, entry); + return ZipDeflate.decompressEntryToBytes(getBuffer(), entry); } catch (Exception e) { entryParseFailed(entry, e); return useFallbackParser(entry).getBytes(); } } // 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) { @@ -361,7 +371,7 @@ public final class JadxZipParser implements IZipParser { } @SuppressWarnings("resource") - private ZipContent initFallbackParser() { + private synchronized ZipContent initFallbackParser() { if (fallbackZipContent == null) { try { fallbackZipContent = new FallbackZipParser(zipFile, options).open(); @@ -378,7 +388,7 @@ public final class JadxZipParser implements IZipParser { } private int readFlags(JadxZipEntry entry) { - ByteBuffer buf = byteBuffer; + ByteBuffer buf = getBuffer(); buf.position(entry.getEntryStart() + 6); return readU2(buf); } @@ -407,6 +417,7 @@ public final class JadxZipParser implements IZipParser { return new String(bytes, StandardCharsets.UTF_8); } + @SuppressWarnings("DataFlowIssue") @Override public void close() throws IOException { try { diff --git a/jadx-core/src/main/java/jadx/api/JadxArgs.java b/jadx-core/src/main/java/jadx/api/JadxArgs.java index 20b6df608..9cc06e6bb 100644 --- a/jadx-core/src/main/java/jadx/api/JadxArgs.java +++ b/jadx-core/src/main/java/jadx/api/JadxArgs.java @@ -95,7 +95,7 @@ public class JadxArgs implements Closeable { /** * Predicate that allows to filter the classes to be process based on their full name */ - private Predicate classFilter = null; + private @Nullable Predicate classFilter = null; /** * Save dependencies for classes accepted by {@code classFilter} @@ -227,7 +227,6 @@ public class JadxArgs implements Closeable { @Override public void close() { try { - inputFiles = null; if (codeCache != null) { codeCache.close(); } @@ -239,9 +238,6 @@ public class JadxArgs implements Closeable { } } catch (Exception e) { LOG.error("Failed to close JadxArgs", e); - } finally { - codeCache = null; - usageInfoCache = null; } } diff --git a/jadx-core/src/main/java/jadx/api/JavaClass.java b/jadx-core/src/main/java/jadx/api/JavaClass.java index d5ee9818a..ea1c8e8d3 100644 --- a/jadx-core/src/main/java/jadx/api/JavaClass.java +++ b/jadx-core/src/main/java/jadx/api/JavaClass.java @@ -6,12 +6,11 @@ import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import jadx.api.metadata.ICodeAnnotation; import jadx.api.metadata.ICodeNodeRef; @@ -26,11 +25,9 @@ import jadx.core.dex.nodes.MethodNode; import jadx.core.utils.ListUtils; public final class JavaClass implements JavaNode { - private static final Logger LOG = LoggerFactory.getLogger(JavaClass.class); - - private final JadxDecompiler decompiler; + private final @Nullable JadxDecompiler decompiler; private final ClassNode cls; - private final JavaClass parent; + private final @Nullable JavaClass parent; private List innerClasses = Collections.emptyList(); private List inlinedClasses = Collections.emptyList(); @@ -38,7 +35,7 @@ public final class JavaClass implements JavaNode { private List methods = Collections.emptyList(); private boolean listsLoaded; - JavaClass(ClassNode classNode, JadxDecompiler decompiler) { + JavaClass(ClassNode classNode, @NotNull JadxDecompiler decompiler) { this.decompiler = decompiler; this.cls = classNode; this.parent = null; @@ -47,7 +44,7 @@ public final class JavaClass implements JavaNode { /** * Inner classes constructor */ - JavaClass(ClassNode classNode, JavaClass parent) { + JavaClass(ClassNode classNode, @NotNull JavaClass parent) { this.decompiler = null; this.cls = classNode; this.parent = parent; @@ -69,6 +66,21 @@ public final class JavaClass implements JavaNode { 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() { listsLoaded = false; return cls.reloadCode(); @@ -187,10 +199,10 @@ public final class JavaClass implements JavaNode { if (parent != null) { 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); } @@ -259,7 +271,7 @@ public final class JavaClass implements JavaNode { } @Override - public JavaClass getDeclaringClass() { + public @Nullable JavaClass getDeclaringClass() { return parent; } @@ -362,21 +374,4 @@ public final class JavaClass implements JavaNode { public String toString() { 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; - } } diff --git a/jadx-core/src/main/java/jadx/api/JavaMethod.java b/jadx-core/src/main/java/jadx/api/JavaMethod.java index 43b06e698..645867c16 100644 --- a/jadx-core/src/main/java/jadx/api/JavaMethod.java +++ b/jadx-core/src/main/java/jadx/api/JavaMethod.java @@ -5,8 +5,6 @@ import java.util.List; import java.util.stream.Collectors; import org.jetbrains.annotations.ApiStatus; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import jadx.api.metadata.ICodeAnnotation; import jadx.api.metadata.ICodeNodeRef; @@ -19,8 +17,6 @@ import jadx.core.dex.nodes.MethodNode; import jadx.core.utils.Utils; public final class JavaMethod implements JavaNode { - private static final Logger LOG = LoggerFactory.getLogger(JavaMethod.class); - private final MethodNode mth; private final JavaClass parent; diff --git a/jadx-core/src/main/java/jadx/core/dex/info/ConstStorage.java b/jadx-core/src/main/java/jadx/core/dex/info/ConstStorage.java index 75b06a3d9..47d2b3214 100644 --- a/jadx-core/src/main/java/jadx/core/dex/info/ConstStorage.java +++ b/jadx-core/src/main/java/jadx/core/dex/info/ConstStorage.java @@ -15,7 +15,6 @@ 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 { @@ -23,18 +22,18 @@ public class ConstStorage { private final Map values = new ConcurrentHashMap<>(); private final Set duplicates = new HashSet<>(); - public Map getValues() { + Map getValues() { return values; } - public IFieldInfoRef get(Object key) { + IFieldInfoRef get(Object key) { return values.get(key); } /** * @return true if this value is duplicated */ - public boolean put(Object value, IFieldInfoRef fld) { + boolean put(Object value, IFieldInfoRef fld) { if (duplicates.contains(value)) { values.remove(value); return true; @@ -85,14 +84,6 @@ public class ConstStorage { 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) { classes.remove(cls); globalValues.removeForCls(cls); diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/args/ArgType.java b/jadx-core/src/main/java/jadx/core/dex/instructions/args/ArgType.java index d99bc8d1a..5575f26b0 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/args/ArgType.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/args/ArgType.java @@ -10,6 +10,8 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.TestOnly; +import com.google.errorprone.annotations.Immutable; + import jadx.core.Consts; import jadx.core.dex.info.ClassInfo; import jadx.core.dex.nodes.RootNode; @@ -18,6 +20,7 @@ import jadx.core.utils.ListUtils; import jadx.core.utils.Utils; import jadx.core.utils.exceptions.JadxRuntimeException; +@Immutable public abstract class ArgType { public static final ArgType INT = primitive(PrimitiveType.INT); public static final ArgType BOOLEAN = primitive(PrimitiveType.BOOLEAN); @@ -200,7 +203,7 @@ public abstract class ArgType { private static final class PrimitiveArg extends KnownType { private final PrimitiveType type; - public PrimitiveArg(PrimitiveType type) { + PrimitiveArg(PrimitiveType type) { this.type = type; this.hash = type.hashCode(); } @@ -229,7 +232,7 @@ public abstract class ArgType { private static class ObjectType extends KnownType { protected final String objName; - public ObjectType(String obj) { + ObjectType(String obj) { this.objName = obj; this.hash = objName.hashCode(); } @@ -263,15 +266,15 @@ public abstract class ArgType { private static final class GenericType extends ObjectType { private List extendTypes; - public GenericType(String obj) { + GenericType(String obj) { this(obj, Collections.emptyList()); } - public GenericType(String obj, ArgType extendType) { + GenericType(String obj, ArgType extendType) { this(obj, Collections.singletonList(extendType)); } - public GenericType(String obj, List extendTypes) { + GenericType(String obj, List extendTypes) { super(obj); this.extendTypes = extendTypes; } @@ -337,7 +340,7 @@ public abstract class ArgType { private final ArgType type; private final WildcardBound bound; - public WildcardType(ArgType obj, WildcardBound bound) { + WildcardType(ArgType obj, WildcardBound bound) { super(OBJECT.getObject()); this.type = Objects.requireNonNull(obj); this.bound = Objects.requireNonNull(bound); @@ -382,7 +385,7 @@ public abstract class ArgType { private static class GenericObject extends ObjectType { private final List generics; - public GenericObject(String obj, List generics) { + GenericObject(String obj, List generics) { super(obj); this.generics = Objects.requireNonNull(generics); this.hash = calcHash(); @@ -418,7 +421,7 @@ public abstract class ArgType { private final ObjectType outerType; private final ObjectType innerType; - public OuterGenericObject(ObjectType outerType, ObjectType innerType) { + OuterGenericObject(ObjectType outerType, ObjectType innerType) { super(outerType.getObject() + '$' + innerType.getObject()); this.outerType = outerType; this.innerType = innerType; @@ -466,7 +469,7 @@ public abstract class ArgType { private static final PrimitiveType[] ARRAY_POSSIBLES = new PrimitiveType[] { PrimitiveType.ARRAY }; private final ArgType arrayElement; - public ArrayArg(ArgType arrayElement) { + ArrayArg(ArgType arrayElement) { this.arrayElement = arrayElement; this.hash = arrayElement.hashCode(); } @@ -526,7 +529,7 @@ public abstract class ArgType { private static final class UnknownArg extends ArgType { private final PrimitiveType[] possibleTypes; - public UnknownArg(PrimitiveType[] types) { + UnknownArg(PrimitiveType[] types) { this.possibleTypes = types; this.hash = Arrays.hashCode(possibleTypes); } diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java index 13f0b645c..ba6fe4a7a 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java @@ -74,7 +74,7 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails, // decompilation data, reset on unload private RegisterArg thisArg; private List argsList; - private InsnNode[] instructions; + private @Nullable InsnNode[] instructions; private List blocks; private int blocksMaxCId; private BlockNode enterBlock; @@ -106,11 +106,12 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails, this.parentClass = classNode; this.accFlags = new AccessInfo(mthData.getAccessFlags(), AFType.METHOD); ICodeReader codeReader = mthData.getCodeReader(); - this.noCode = codeReader == null; - if (noCode) { + if (codeReader == null) { + this.noCode = true; this.codeReader = null; this.insnsCount = 0; } else { + this.noCode = false; this.codeReader = codeReader.copy(); this.insnsCount = codeReader.getUnitsCount(); } @@ -713,6 +714,7 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails, } // Cannot modify through get, use setUseIn + @Override public List getUseIn() { return Collections.unmodifiableList(useIn); } @@ -721,7 +723,7 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails, public void setUseIn(List 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) { methodUsedIn.addUsed(this); } diff --git a/jadx-core/src/main/java/jadx/core/utils/StringUtils.java b/jadx-core/src/main/java/jadx/core/utils/StringUtils.java index c45bb88ea..09fa334b4 100644 --- a/jadx-core/src/main/java/jadx/core/utils/StringUtils.java +++ b/jadx-core/src/main/java/jadx/core/utils/StringUtils.java @@ -209,7 +209,7 @@ public class StringUtils { return sb.toString(); } - private static String escapeXmlChar(char c) { + private static @Nullable String escapeXmlChar(char c) { if (c <= 0x1F) { 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) { case '\n': return "\\n"; diff --git a/jadx-core/src/main/java/jadx/core/utils/Utils.java b/jadx-core/src/main/java/jadx/core/utils/Utils.java index c142b2bd3..1c4f8188a 100644 --- a/jadx-core/src/main/java/jadx/core/utils/Utils.java +++ b/jadx-core/src/main/java/jadx/core/utils/Utils.java @@ -3,6 +3,7 @@ package jadx.core.utils; import java.io.OutputStream; import java.io.PrintWriter; import java.io.StringWriter; +import java.nio.charset.StandardCharsets; import java.util.ArrayDeque; import java.util.ArrayList; 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); throwable.printStackTrace(pw); pw.flush(); @@ -618,7 +619,7 @@ public class Utils { private final AtomicInteger number = new AtomicInteger(0); private final String name; - public SimpleThreadFactory(String name) { + SimpleThreadFactory(String name) { this.name = name; } diff --git a/jadx-plugins/jadx-input-api/src/main/java/jadx/api/plugins/input/data/IMethodHandle.java b/jadx-plugins/jadx-input-api/src/main/java/jadx/api/plugins/input/data/IMethodHandle.java index 2c41f2b1c..9f6701a88 100644 --- a/jadx-plugins/jadx-input-api/src/main/java/jadx/api/plugins/input/data/IMethodHandle.java +++ b/jadx-plugins/jadx-input-api/src/main/java/jadx/api/plugins/input/data/IMethodHandle.java @@ -1,11 +1,15 @@ package jadx.api.plugins.input.data; +import org.jetbrains.annotations.Nullable; + public interface IMethodHandle { MethodHandleType getType(); + @Nullable IFieldRef getFieldRef(); + @Nullable IMethodRef getMethodRef(); void load(); diff --git a/jadx-plugins/jadx-input-api/src/main/java/jadx/api/plugins/input/data/annotations/EncodedValue.java b/jadx-plugins/jadx-input-api/src/main/java/jadx/api/plugins/input/data/annotations/EncodedValue.java index 77b54554c..ddba5393c 100644 --- a/jadx-plugins/jadx-input-api/src/main/java/jadx/api/plugins/input/data/annotations/EncodedValue.java +++ b/jadx-plugins/jadx-input-api/src/main/java/jadx/api/plugins/input/data/annotations/EncodedValue.java @@ -8,7 +8,7 @@ import jadx.api.plugins.input.data.attributes.JadxAttrType; import jadx.api.plugins.input.data.attributes.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 Object value; diff --git a/jadx-plugins/jadx-input-api/src/main/java/jadx/api/plugins/input/data/annotations/IAnnotation.java b/jadx-plugins/jadx-input-api/src/main/java/jadx/api/plugins/input/data/annotations/IAnnotation.java index 5d0a702d4..b2d17c9a8 100644 --- a/jadx-plugins/jadx-input-api/src/main/java/jadx/api/plugins/input/data/annotations/IAnnotation.java +++ b/jadx-plugins/jadx-input-api/src/main/java/jadx/api/plugins/input/data/annotations/IAnnotation.java @@ -11,8 +11,7 @@ public interface IAnnotation { Map getValues(); - @Nullable - default EncodedValue getDefaultValue() { + default @Nullable EncodedValue getDefaultValue() { return getValues().get("value"); } } diff --git a/jadx-plugins/jadx-input-api/src/main/java/jadx/api/plugins/input/data/attributes/types/AnnotationMethodParamsAttr.java b/jadx-plugins/jadx-input-api/src/main/java/jadx/api/plugins/input/data/attributes/types/AnnotationMethodParamsAttr.java index 9849e3cf6..e0e638384 100644 --- a/jadx-plugins/jadx-input-api/src/main/java/jadx/api/plugins/input/data/attributes/types/AnnotationMethodParamsAttr.java +++ b/jadx-plugins/jadx-input-api/src/main/java/jadx/api/plugins/input/data/attributes/types/AnnotationMethodParamsAttr.java @@ -11,8 +11,7 @@ import jadx.api.plugins.input.data.attributes.PinnedAttribute; public class AnnotationMethodParamsAttr extends PinnedAttribute { - @Nullable - public static AnnotationMethodParamsAttr pack(List> annotationRefList) { + public static @Nullable AnnotationMethodParamsAttr pack(List> annotationRefList) { if (annotationRefList.isEmpty()) { return null; } diff --git a/jadx-plugins/jadx-input-api/src/main/java/jadx/api/plugins/input/data/attributes/types/AnnotationsAttr.java b/jadx-plugins/jadx-input-api/src/main/java/jadx/api/plugins/input/data/attributes/types/AnnotationsAttr.java index b84e104ca..6ec4c5ad1 100644 --- a/jadx-plugins/jadx-input-api/src/main/java/jadx/api/plugins/input/data/attributes/types/AnnotationsAttr.java +++ b/jadx-plugins/jadx-input-api/src/main/java/jadx/api/plugins/input/data/attributes/types/AnnotationsAttr.java @@ -16,8 +16,7 @@ import jadx.api.plugins.input.data.attributes.PinnedAttribute; public class AnnotationsAttr extends PinnedAttribute { - @Nullable - public static AnnotationsAttr pack(List annotationList) { + public static @Nullable AnnotationsAttr pack(List annotationList) { if (annotationList.isEmpty()) { return null; } @@ -39,7 +38,7 @@ public class AnnotationsAttr extends PinnedAttribute { this.map = map; } - public IAnnotation get(String className) { + public @Nullable IAnnotation get(String className) { return map.get(className); } diff --git a/jadx-plugins/jadx-input-api/src/main/java/jadx/api/plugins/input/data/attributes/types/MethodParametersAttr.java b/jadx-plugins/jadx-input-api/src/main/java/jadx/api/plugins/input/data/attributes/types/MethodParametersAttr.java index 84d291296..9e833c16d 100644 --- a/jadx-plugins/jadx-input-api/src/main/java/jadx/api/plugins/input/data/attributes/types/MethodParametersAttr.java +++ b/jadx-plugins/jadx-input-api/src/main/java/jadx/api/plugins/input/data/attributes/types/MethodParametersAttr.java @@ -27,6 +27,7 @@ public class MethodParametersAttr extends PinnedAttribute { return name; } + @Override public String toString() { return AccessFlags.format(accFlags, AccessFlagsScope.METHOD) + name; } diff --git a/jadx-plugins/jadx-input-api/src/main/java/jadx/api/plugins/input/data/impl/FieldRefHandle.java b/jadx-plugins/jadx-input-api/src/main/java/jadx/api/plugins/input/data/impl/FieldRefHandle.java index 7fe6d39fa..272ee9851 100644 --- a/jadx-plugins/jadx-input-api/src/main/java/jadx/api/plugins/input/data/impl/FieldRefHandle.java +++ b/jadx-plugins/jadx-input-api/src/main/java/jadx/api/plugins/input/data/impl/FieldRefHandle.java @@ -1,5 +1,7 @@ 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.IMethodHandle; import jadx.api.plugins.input.data.IMethodRef; @@ -21,12 +23,12 @@ public class FieldRefHandle implements IMethodHandle { } @Override - public IFieldRef getFieldRef() { + public @Nullable IFieldRef getFieldRef() { return fieldRef; } @Override - public IMethodRef getMethodRef() { + public @Nullable IMethodRef getMethodRef() { return null; } diff --git a/jadx-plugins/jadx-input-api/src/main/java/jadx/api/plugins/input/data/impl/ListConsumer.java b/jadx-plugins/jadx-input-api/src/main/java/jadx/api/plugins/input/data/impl/ListConsumer.java index 2202fa480..48e1f0257 100644 --- a/jadx-plugins/jadx-input-api/src/main/java/jadx/api/plugins/input/data/impl/ListConsumer.java +++ b/jadx-plugins/jadx-input-api/src/main/java/jadx/api/plugins/input/data/impl/ListConsumer.java @@ -9,7 +9,7 @@ import jadx.api.plugins.input.data.ISeqConsumer; public class ListConsumer implements ISeqConsumer { private final Function convert; - private List list; + private List list = new ArrayList<>(); public ListConsumer(Function convert) { this.convert = convert; @@ -26,10 +26,6 @@ public class ListConsumer implements ISeqConsumer { } public List getResult() { - if (list == null) { - // init not called - return Collections.emptyList(); - } return list; } } diff --git a/jadx-plugins/jadx-input-api/src/main/java/jadx/api/plugins/input/data/impl/MethodRefHandle.java b/jadx-plugins/jadx-input-api/src/main/java/jadx/api/plugins/input/data/impl/MethodRefHandle.java index 8a9035ec6..4e500a2d1 100644 --- a/jadx-plugins/jadx-input-api/src/main/java/jadx/api/plugins/input/data/impl/MethodRefHandle.java +++ b/jadx-plugins/jadx-input-api/src/main/java/jadx/api/plugins/input/data/impl/MethodRefHandle.java @@ -1,5 +1,7 @@ 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.IMethodHandle; import jadx.api.plugins.input.data.IMethodRef; @@ -21,12 +23,12 @@ public class MethodRefHandle implements IMethodHandle { } @Override - public IMethodRef getMethodRef() { + public @Nullable IMethodRef getMethodRef() { return methodRef; } @Override - public IFieldData getFieldRef() { + public @Nullable IFieldData getFieldRef() { return null; }