fix: handle OutOfMemoryError to correctly halt processing (#2676)
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user