fix: handle OutOfMemoryError to correctly halt processing (#2676)

This commit is contained in:
Skylot
2025-10-29 21:03:58 +00:00
parent f17c46224d
commit d191f62b8d
12 changed files with 60 additions and 20 deletions
@@ -29,7 +29,7 @@ public class DecompilePassWrapper extends AbstractVisitor implements IPassWrappe
public void init(RootNode root) throws JadxException {
try {
decompilePass.init(root);
} catch (Throwable e) {
} catch (StackOverflowError | Exception e) {
LOG.error("Error in decompile pass init: {}", this, e);
}
}
@@ -38,7 +38,7 @@ public class DecompilePassWrapper extends AbstractVisitor implements IPassWrappe
public boolean visit(ClassNode cls) throws JadxException {
try {
return decompilePass.visit(cls);
} catch (Throwable e) {
} catch (StackOverflowError | Exception e) {
cls.addError("Error in decompile pass: " + this, e);
return false;
}
@@ -48,7 +48,7 @@ public class DecompilePassWrapper extends AbstractVisitor implements IPassWrappe
public void visit(MethodNode mth) throws JadxException {
try {
decompilePass.visit(mth);
} catch (Throwable e) {
} catch (StackOverflowError | Exception e) {
mth.addError("Error in decompile pass: " + this, e);
}
}
@@ -16,6 +16,7 @@ import jadx.core.dex.nodes.LoadStage;
import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.visitors.DepthTraversal;
import jadx.core.dex.visitors.IDexTreeVisitor;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxRuntimeException;
import static jadx.core.dex.nodes.ProcessState.GENERATED_AND_UNLOADED;
@@ -41,6 +42,7 @@ public class ProcessClass {
// nothing to do
return null;
}
Utils.checkThreadInterrupt();
synchronized (cls.getClassInfo()) {
try {
if (cls.contains(AFlag.CLASS_DEEP_RELOAD)) {
@@ -76,6 +78,7 @@ public class ProcessClass {
cls.setState(PROCESS_COMPLETE);
}
if (codegen) {
Utils.checkThreadInterrupt();
ICodeInfo code = CodeGen.generate(cls);
if (!cls.contains(AFlag.DONT_UNLOAD_CLASS)) {
cls.unload();
@@ -84,7 +87,7 @@ public class ProcessClass {
return code;
}
return null;
} catch (Throwable e) {
} catch (StackOverflowError | Exception e) {
if (codegen) {
throw e;
}
@@ -119,7 +122,7 @@ public class ProcessClass {
throw new JadxRuntimeException("Codegen failed");
}
return code;
} catch (Throwable e) {
} catch (StackOverflowError | Exception e) {
throw new JadxRuntimeException("Failed to generate code for class: " + cls.getFullName(), e);
}
}
@@ -135,7 +138,7 @@ public class ProcessClass {
}
try {
process(cls, false);
} catch (Throwable e) {
} catch (StackOverflowError | Exception e) {
throw new JadxRuntimeException("Failed to process class: " + cls.getFullName(), e);
}
}
@@ -146,7 +149,7 @@ public class ProcessClass {
public @Nullable ICodeInfo forceGenerateCode(ClassNode cls) {
try {
return process(cls, true);
} catch (Throwable e) {
} catch (StackOverflowError | Exception e) {
throw new JadxRuntimeException("Failed to generate code for class: " + cls.getFullName(), e);
}
}
@@ -403,7 +403,7 @@ public class ClassNode extends NotificationAttrNode
ICodeInfo codeInfo = root.getProcessClasses().generateCode(this);
processDefinitionAnnotations(codeInfo);
return codeInfo;
} catch (Throwable e) {
} catch (StackOverflowError | Exception e) {
addError("Code generation failed", e);
return new SimpleCodeInfo(Utils.getStackTrace(e));
}
@@ -874,7 +874,7 @@ public class ClassNode extends NotificationAttrNode
code.startLine(String.format("###### Class %s (%s)", getFullName(), getRawName()));
try {
code.startLine(clsData.getDisassembledCode());
} catch (Throwable e) {
} catch (Exception e) {
code.startLine("Failed to disassemble class:");
code.startLine(Utils.getStackTrace(e));
}
@@ -64,7 +64,7 @@ public class ProcessAnonymous extends AbstractVisitor {
private static void processClass(ClassNode cls) {
try {
markAnonymousClass(cls);
} catch (Throwable e) {
} catch (StackOverflowError | Exception e) {
cls.addError("Anonymous visitor error", e);
}
}
@@ -61,7 +61,7 @@ public class PostDominatorTree {
}
mth.addInfoComment("Infinite loop detected, blocks: " + blocksDelta + ", insns: " + insnsCount);
}
} catch (Throwable e) {
} catch (StackOverflowError | Exception e) {
// show error as a warning because this info not always used
mth.addWarnComment("Failed to build post-dominance tree", e);
} finally {
@@ -118,7 +118,7 @@ public class SwitchOverStringVisitor extends AbstractVisitor implements IRegionI
// use string arg directly in switch
swInsn.replaceArg(swInsn.getArg(0), strArg.duplicate());
return true;
} catch (Throwable e) {
} catch (StackOverflowError | Exception e) {
mth.addWarnComment("Failed to restore switch over string. Please report as a decompilation issue", e);
return false;
}
@@ -151,7 +151,7 @@ public class SwitchOverStringVisitor extends AbstractVisitor implements IRegionI
}
}
InsnRemover.removeAllMarked(mth);
} catch (Throwable e) {
} catch (StackOverflowError | Exception e) {
mth.addWarnComment("Failed to clean up code after switch over string restore", e);
}
}
@@ -116,7 +116,7 @@ public class AndroidGradleGenerator implements IExportGradleGenerator {
IJadxSecurity security = root.getArgs().getSecurity();
AndroidManifestParser parser = new AndroidManifestParser(androidManifest, strings, attrs, security);
return parser.parse();
} catch (Throwable t) {
} catch (Exception t) {
LOG.warn("Failed to parse AndroidManifest.xml", t);
return UNKNOWN_APP_PARAMS;
}
@@ -23,7 +23,7 @@ public class DebugChecksPass extends AbstractVisitor {
if (!mth.contains(AType.JADX_ERROR)) {
try {
DebugChecks.runChecksAfterVisitor(mth, visitorName);
} catch (Throwable e) {
} catch (Exception e) {
mth.addError("Check error", e);
}
}
@@ -35,7 +35,7 @@ public class DecompilerScheduler implements IDecompileScheduler {
check(result, classes);
}
return result;
} catch (Throwable e) {
} catch (Exception e) {
LOG.warn("Build batches failed (continue with fallback)", e);
return buildFallback(classes);
}
@@ -23,6 +23,8 @@ import java.util.function.Function;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.ICodeWriter;
import jadx.api.JadxDecompiler;
@@ -512,6 +514,8 @@ public class Utils {
}
private static final class SimpleThreadFactory implements ThreadFactory {
private static final Logger LOG = LoggerFactory.getLogger(Utils.class);
private static final AtomicInteger POOL = new AtomicInteger(0);
private final AtomicInteger number = new AtomicInteger(0);
private final String name;
@@ -522,9 +526,18 @@ public class Utils {
@Override
public Thread newThread(@NotNull Runnable r) {
return new Thread(r, "jadx-" + name
Thread thread = new Thread(r, "jadx-" + name
+ '-' + POOL.incrementAndGet()
+ '-' + number.incrementAndGet());
thread.setUncaughtExceptionHandler((t, e) -> {
if (e instanceof OutOfMemoryError) {
thread.interrupt();
LOG.error("OutOfMemoryError in thread: {}, forcing interrupt", t.getName());
} else {
LOG.error("Uncaught thread exception, thread: {}", t.getName(), e);
}
});
return thread;
}
}
@@ -10,6 +10,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.JadxArgs;
import jadx.api.utils.tasks.ITaskExecutor;
@@ -17,6 +19,7 @@ import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxRuntimeException;
public class TaskExecutor implements ITaskExecutor {
private static final Logger LOG = LoggerFactory.getLogger(TaskExecutor.class);
private enum ExecType {
PARALLEL,
@@ -49,6 +52,7 @@ public class TaskExecutor implements ITaskExecutor {
private final Object executorSync = new Object();
private @Nullable ExecutorService executor;
private int tasksCount = 0;
private @Nullable Error terminateError;
@Override
public void addParallelTasks(List<? extends Runnable> parallelTasks) {
@@ -124,6 +128,10 @@ public class TaskExecutor implements ITaskExecutor {
if (activeExecutor != null && running.get()) {
awaitExecutorTermination(activeExecutor);
}
Error error = terminateError;
if (error != null) {
throw error;
}
}
@Override
@@ -131,6 +139,16 @@ public class TaskExecutor implements ITaskExecutor {
terminating.set(true);
}
@SuppressWarnings("DataFlowIssue")
private void terminateWithError(Error error) {
if (terminating.get()) {
return;
}
terminateError = error;
terminate();
executor.shutdownNow();
}
@Override
public boolean isTerminating() {
return terminating.get();
@@ -176,8 +194,14 @@ public class TaskExecutor implements ITaskExecutor {
if (terminating.get()) {
return;
}
task.run();
progress.incrementAndGet();
try {
task.run();
progress.incrementAndGet();
} catch (Error e) {
terminateWithError(e);
} catch (Exception e) {
LOG.error("Unhandled task exception:", e);
}
}
public static void awaitExecutorTermination(ExecutorService executor) {
@@ -34,7 +34,7 @@ public class ResourcesSaver implements Runnable {
public void run() {
try {
saveResources(resourceFile.loadContent());
} catch (Throwable e) {
} catch (StackOverflowError | Exception e) {
LOG.warn("Failed to save resource: {}", resourceFile.getOriginalName(), e);
}
}