From d191f62b8dcb0058255125b86a3df8bbf518d981 Mon Sep 17 00:00:00 2001 From: Skylot <118523+skylot@users.noreply.github.com> Date: Wed, 29 Oct 2025 21:03:58 +0000 Subject: [PATCH] fix: handle `OutOfMemoryError` to correctly halt processing (#2676) --- .../api/impl/passes/DecompilePassWrapper.java | 6 ++-- .../src/main/java/jadx/core/ProcessClass.java | 11 +++++--- .../java/jadx/core/dex/nodes/ClassNode.java | 4 +-- .../core/dex/visitors/ProcessAnonymous.java | 2 +- .../visitors/blocks/PostDominatorTree.java | 2 +- .../regions/SwitchOverStringVisitor.java | 4 +-- .../export/gen/AndroidGradleGenerator.java | 2 +- .../java/jadx/core/utils/DebugChecksPass.java | 2 +- .../jadx/core/utils/DecompilerScheduler.java | 2 +- .../src/main/java/jadx/core/utils/Utils.java | 15 +++++++++- .../jadx/core/utils/tasks/TaskExecutor.java | 28 +++++++++++++++++-- .../java/jadx/core/xmlgen/ResourcesSaver.java | 2 +- 12 files changed, 60 insertions(+), 20 deletions(-) diff --git a/jadx-core/src/main/java/jadx/api/impl/passes/DecompilePassWrapper.java b/jadx-core/src/main/java/jadx/api/impl/passes/DecompilePassWrapper.java index 6795df6a6..a7c5747be 100644 --- a/jadx-core/src/main/java/jadx/api/impl/passes/DecompilePassWrapper.java +++ b/jadx-core/src/main/java/jadx/api/impl/passes/DecompilePassWrapper.java @@ -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); } } diff --git a/jadx-core/src/main/java/jadx/core/ProcessClass.java b/jadx-core/src/main/java/jadx/core/ProcessClass.java index 702db2e0d..35e4e7d05 100644 --- a/jadx-core/src/main/java/jadx/core/ProcessClass.java +++ b/jadx-core/src/main/java/jadx/core/ProcessClass.java @@ -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); } } diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java index 65d57eef8..e75f0662c 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java @@ -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)); } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/ProcessAnonymous.java b/jadx-core/src/main/java/jadx/core/dex/visitors/ProcessAnonymous.java index 2e72e4f2a..6f3b7c0e4 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/ProcessAnonymous.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/ProcessAnonymous.java @@ -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); } } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/blocks/PostDominatorTree.java b/jadx-core/src/main/java/jadx/core/dex/visitors/blocks/PostDominatorTree.java index e09decd9f..927b9f349 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/blocks/PostDominatorTree.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/blocks/PostDominatorTree.java @@ -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 { diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/regions/SwitchOverStringVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/regions/SwitchOverStringVisitor.java index 4467656e0..81919f782 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/regions/SwitchOverStringVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/regions/SwitchOverStringVisitor.java @@ -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); } } diff --git a/jadx-core/src/main/java/jadx/core/export/gen/AndroidGradleGenerator.java b/jadx-core/src/main/java/jadx/core/export/gen/AndroidGradleGenerator.java index 847debf72..f44a00f7a 100644 --- a/jadx-core/src/main/java/jadx/core/export/gen/AndroidGradleGenerator.java +++ b/jadx-core/src/main/java/jadx/core/export/gen/AndroidGradleGenerator.java @@ -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; } diff --git a/jadx-core/src/main/java/jadx/core/utils/DebugChecksPass.java b/jadx-core/src/main/java/jadx/core/utils/DebugChecksPass.java index 0b75b2784..4a413a839 100644 --- a/jadx-core/src/main/java/jadx/core/utils/DebugChecksPass.java +++ b/jadx-core/src/main/java/jadx/core/utils/DebugChecksPass.java @@ -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); } } diff --git a/jadx-core/src/main/java/jadx/core/utils/DecompilerScheduler.java b/jadx-core/src/main/java/jadx/core/utils/DecompilerScheduler.java index 44c5462a8..a75545672 100644 --- a/jadx-core/src/main/java/jadx/core/utils/DecompilerScheduler.java +++ b/jadx-core/src/main/java/jadx/core/utils/DecompilerScheduler.java @@ -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); } 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 2f7afac36..0079bcaee 100644 --- a/jadx-core/src/main/java/jadx/core/utils/Utils.java +++ b/jadx-core/src/main/java/jadx/core/utils/Utils.java @@ -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; } } diff --git a/jadx-core/src/main/java/jadx/core/utils/tasks/TaskExecutor.java b/jadx-core/src/main/java/jadx/core/utils/tasks/TaskExecutor.java index 334b497f5..6c7230c32 100644 --- a/jadx-core/src/main/java/jadx/core/utils/tasks/TaskExecutor.java +++ b/jadx-core/src/main/java/jadx/core/utils/tasks/TaskExecutor.java @@ -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 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) { diff --git a/jadx-core/src/main/java/jadx/core/xmlgen/ResourcesSaver.java b/jadx-core/src/main/java/jadx/core/xmlgen/ResourcesSaver.java index 387121af1..e52f5caeb 100644 --- a/jadx-core/src/main/java/jadx/core/xmlgen/ResourcesSaver.java +++ b/jadx-core/src/main/java/jadx/core/xmlgen/ResourcesSaver.java @@ -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); } }