refactor: add ErrorProne and NullAway checks, fix some issues

This commit is contained in:
Skylot
2026-04-06 21:13:08 +01:00
parent 7ce40baacb
commit 1e908f7af3
29 changed files with 264 additions and 166 deletions
+2
View File
@@ -39,5 +39,7 @@ jadx-output/
*.orig
quark.json
.env
cliff.toml
jadx-gui/src/main/resources/logback.xml
+46 -2
View File
@@ -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<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 {
isCanBeConsumed = false
}
+2
View File
@@ -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 {
@@ -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<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;
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();
}
}
@@ -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<ProjectDirectories, String> dirFunc) throws IOException {
private static Path loadEnvDir(String envVar, Supplier<String> 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;
}
}
@@ -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);
}
}
@@ -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 {
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<IZipEntry> list = new ArrayList<>();
Enumeration<? extends ZipEntry> 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<IZipEntry> list = new ArrayList<>();
Enumeration<? extends ZipEntry> 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();
}
}
}
@@ -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()) {
@@ -35,6 +35,7 @@ public final class JadxZipEntry implements IZipEntry {
return compressedSize <= uncompressedSize;
}
@Override
public String getName() {
return fileName;
}
@@ -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<IZipEntry> 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 {
@@ -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<String> classFilter = null;
private @Nullable Predicate<String> 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;
}
}
+23 -28
View File
@@ -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<JavaClass> innerClasses = 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 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;
}
}
@@ -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;
@@ -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<Object, IFieldInfoRef> values = new ConcurrentHashMap<>();
private final Set<Object> duplicates = new HashSet<>();
public Map<Object, IFieldInfoRef> getValues() {
Map<Object, IFieldInfoRef> 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);
@@ -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<ArgType> 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<ArgType> extendTypes) {
GenericType(String obj, List<ArgType> 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<ArgType> generics;
public GenericObject(String obj, List<ArgType> generics) {
GenericObject(String obj, List<ArgType> 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);
}
@@ -74,7 +74,7 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
// decompilation data, reset on unload
private RegisterArg thisArg;
private List<RegisterArg> argsList;
private InsnNode[] instructions;
private @Nullable InsnNode[] instructions;
private List<BlockNode> 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<MethodNode> getUseIn() {
return Collections.unmodifiableList(useIn);
}
@@ -721,7 +723,7 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
public void setUseIn(List<MethodNode> 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);
}
@@ -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";
@@ -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;
}
@@ -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();
@@ -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;
@@ -11,8 +11,7 @@ public interface IAnnotation {
Map<String, EncodedValue> getValues();
@Nullable
default EncodedValue getDefaultValue() {
default @Nullable EncodedValue getDefaultValue() {
return getValues().get("value");
}
}
@@ -11,8 +11,7 @@ import jadx.api.plugins.input.data.attributes.PinnedAttribute;
public class AnnotationMethodParamsAttr extends PinnedAttribute {
@Nullable
public static AnnotationMethodParamsAttr pack(List<List<IAnnotation>> annotationRefList) {
public static @Nullable AnnotationMethodParamsAttr pack(List<List<IAnnotation>> annotationRefList) {
if (annotationRefList.isEmpty()) {
return null;
}
@@ -16,8 +16,7 @@ import jadx.api.plugins.input.data.attributes.PinnedAttribute;
public class AnnotationsAttr extends PinnedAttribute {
@Nullable
public static AnnotationsAttr pack(List<IAnnotation> annotationList) {
public static @Nullable AnnotationsAttr pack(List<IAnnotation> 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);
}
@@ -27,6 +27,7 @@ public class MethodParametersAttr extends PinnedAttribute {
return name;
}
@Override
public String toString() {
return AccessFlags.format(accFlags, AccessFlagsScope.METHOD) + name;
}
@@ -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;
}
@@ -9,7 +9,7 @@ import jadx.api.plugins.input.data.ISeqConsumer;
public class ListConsumer<T, R> implements ISeqConsumer<T> {
private final Function<T, R> convert;
private List<R> list;
private List<R> list = new ArrayList<>();
public ListConsumer(Function<T, R> convert) {
this.convert = convert;
@@ -26,10 +26,6 @@ public class ListConsumer<T, R> implements ISeqConsumer<T> {
}
public List<R> getResult() {
if (list == null) {
// init not called
return Collections.emptyList();
}
return list;
}
}
@@ -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;
}