diff --git a/src/org/jetbrains/java/decompiler/main/ClassWriter.java b/src/org/jetbrains/java/decompiler/main/ClassWriter.java index ad16b9e065..c9e6dcc27e 100644 --- a/src/org/jetbrains/java/decompiler/main/ClassWriter.java +++ b/src/org/jetbrains/java/decompiler/main/ClassWriter.java @@ -8,6 +8,7 @@ import org.jetbrains.java.decompiler.code.InstructionSequence; import org.jetbrains.java.decompiler.main.ClassesProcessor.ClassNode; import org.jetbrains.java.decompiler.main.collectors.ImportCollector; +import org.jetbrains.java.decompiler.main.decompiler.CancelationManager; import org.jetbrains.java.decompiler.main.extern.IFernflowerLogger; import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences; import org.jetbrains.java.decompiler.main.rels.ClassWrapper; @@ -80,6 +81,8 @@ private static boolean invokeProcessors(TextBuffer buffer, ClassNode node) { if (method.root != null) { try { SwitchHelper.simplifySwitches(method.root, method.methodStruct, method.root); + } catch (CancelationManager.CanceledException e) { + throw e; } catch (Throwable e) { DecompilerContext.getLogger().writeMessage("Method " + method.methodStruct.getName() + " " + method.methodStruct.getDescriptor() + " in class " + node.classStruct.qualifiedName + " couldn't be written.", IFernflowerLogger.Severity.WARN, @@ -113,6 +116,8 @@ private static boolean invokeProcessors(TextBuffer buffer, ClassNode node) { mw.varproc.rerunClashing(mw.root); } } + } catch (CancelationManager.CanceledException e) { + throw e; } catch (Throwable t) { DecompilerContext.getLogger().writeMessage("Class " + node.simpleName + " couldn't be written.", IFernflowerLogger.Severity.WARN, @@ -250,6 +255,9 @@ public void classLambdaToJava(ClassNode node, TextBuffer buffer, Exprent method_ codeBuffer.addBytecodeMapping(root.getDummyExit().bytecode); buffer.append(codeBuffer, node.classStruct.qualifiedName, InterpreterUtil.makeUniqueKey(methodWrapper.methodStruct.getName(), methodWrapper.methodStruct.getDescriptor())); } + catch (CancelationManager.CanceledException e) { + throw e; + } catch (Throwable ex) { DecompilerContext.getLogger().writeMessage("Method " + mt.getName() + " " + mt.getDescriptor() + " in class " + node.classStruct.qualifiedName + " couldn't be written.", IFernflowerLogger.Severity.WARN, @@ -894,6 +902,9 @@ private static void methodLambdaToJava(ClassNode lambdaNode, childBuf.addBytecodeMapping(root.getDummyExit().bytecode); buffer.append(childBuf, classWrapper.getClassStruct().qualifiedName, InterpreterUtil.makeUniqueKey(mt.getName(), mt.getDescriptor())); } + catch (CancelationManager.CanceledException e) { + throw e; + } catch (Throwable t) { String message = "Method " + mt.getName() + " " + mt.getDescriptor() + " in class " + lambdaNode.classStruct.qualifiedName + " couldn't be written."; DecompilerContext.getLogger().writeMessage(message, IFernflowerLogger.Severity.WARN, t); @@ -1216,6 +1227,9 @@ else if (methodWrapper.varproc.getVarFinal(new VarVersionPair(index, 0)) == VarT buffer.append(code, cl.qualifiedName, InterpreterUtil.makeUniqueKey(mt.getName(), mt.getDescriptor())); } } + catch (CancelationManager.CanceledException e) { + throw e; + } catch (Throwable t) { String message = "Method " + mt.getName() + " " + mt.getDescriptor() + " in class " + node.classStruct.qualifiedName + " couldn't be written."; DecompilerContext.getLogger().writeMessage(message, IFernflowerLogger.Severity.WARN, t); diff --git a/src/org/jetbrains/java/decompiler/main/ClassesProcessor.java b/src/org/jetbrains/java/decompiler/main/ClassesProcessor.java index 127b25beee..ae11473f79 100644 --- a/src/org/jetbrains/java/decompiler/main/ClassesProcessor.java +++ b/src/org/jetbrains/java/decompiler/main/ClassesProcessor.java @@ -8,6 +8,7 @@ import org.jetbrains.java.decompiler.code.InstructionSequence; import org.jetbrains.java.decompiler.main.collectors.BytecodeSourceMapper; import org.jetbrains.java.decompiler.main.collectors.ImportCollector; +import org.jetbrains.java.decompiler.main.decompiler.CancelationManager; import org.jetbrains.java.decompiler.main.extern.IFernflowerLogger; import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences; import org.jetbrains.java.decompiler.main.extern.IIdentifierRenamer; @@ -432,6 +433,8 @@ public void processClass(StructClass cl) throws IOException { new NestedMemberAccess().propagateMemberAccess(root); } } + } catch (CancelationManager.CanceledException e) { + throw e; } finally { DecompilerContext.getLogger().endProcessingClass(); } @@ -501,8 +504,9 @@ else if (moduleInfo) { } } } - } - finally { + } catch (CancelationManager.CanceledException e) { + throw e; + } finally { destroyWrappers(root); DecompilerContext.getLogger().endReadingClass(); } diff --git a/src/org/jetbrains/java/decompiler/main/InitializerProcessor.java b/src/org/jetbrains/java/decompiler/main/InitializerProcessor.java index 458d624b9e..a660958326 100644 --- a/src/org/jetbrains/java/decompiler/main/InitializerProcessor.java +++ b/src/org/jetbrains/java/decompiler/main/InitializerProcessor.java @@ -3,6 +3,7 @@ import org.jetbrains.java.decompiler.code.CodeConstants; import org.jetbrains.java.decompiler.main.ClassesProcessor.ClassNode; +import org.jetbrains.java.decompiler.main.decompiler.CancelationManager; import org.jetbrains.java.decompiler.main.extern.IFernflowerLogger; import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences; import org.jetbrains.java.decompiler.main.rels.ClassWrapper; @@ -29,6 +30,8 @@ public static void extractInitializers(ClassWrapper wrapper) { if (method != null && method.root != null) { // successfully decompiled static constructor extractStaticInitializers(wrapper, method); } + } catch (CancelationManager.CanceledException e) { + throw e; } catch (Throwable t) { StructMethod mt = method.methodStruct; String message = "Method " + mt.getName() + " " + mt.getDescriptor() + " in class " + wrapper.getClassStruct().qualifiedName + " couldn't be written."; diff --git a/src/org/jetbrains/java/decompiler/main/decompiler/CancelationManager.java b/src/org/jetbrains/java/decompiler/main/decompiler/CancelationManager.java new file mode 100644 index 0000000000..1e692e331d --- /dev/null +++ b/src/org/jetbrains/java/decompiler/main/decompiler/CancelationManager.java @@ -0,0 +1,49 @@ +package org.jetbrains.java.decompiler.main.decompiler; + +/** + * Used for cancelling the decompilation process. This can for example be useful in GUI frontends with cancelation + * support. + */ +public final class CancelationManager { + private static Runnable cancelationChecker = () -> {}; + + private CancelationManager() { + } + + /** + * Cancels the decompilation process by throwing a {@linkplain CanceledException}. + */ + public static void cancel() { + throw CanceledException.INSTANCE; + } + + /** + * Polled frequently by the decompiler to check if decompilation has been canceled. Use + * {@linkplain #setCancelationChecker(Runnable)} to set the logic for checking cancelation. + */ + public static void checkCanceled() { + cancelationChecker.run(); + } + + /** + * Sets the logic for checking cancelation. To cancel decompilation, call {@linkplain #cancel()} inside the checker. + */ + public static void setCancelationChecker(Runnable checker) { + cancelationChecker = checker; + } + + /** + * The exception that is thrown upon cancelation. + */ + public static final class CanceledException extends RuntimeException { + public static final CanceledException INSTANCE = new CanceledException(); + + private CanceledException() { + } + + @Override + public Throwable fillInStackTrace() { + return this; + } + } +} diff --git a/src/org/jetbrains/java/decompiler/main/decompiler/ConsoleDecompiler.java b/src/org/jetbrains/java/decompiler/main/decompiler/ConsoleDecompiler.java index 7875b56348..4ae7f57342 100644 --- a/src/org/jetbrains/java/decompiler/main/decompiler/ConsoleDecompiler.java +++ b/src/org/jetbrains/java/decompiler/main/decompiler/ConsoleDecompiler.java @@ -180,7 +180,11 @@ else if (arg.startsWith("-only=") || arg.startsWith("--only=")) { decompiler.addWhitelist(prefix); } - decompiler.decompileContext(); + try { + decompiler.decompileContext(); + } catch (CancelationManager.CanceledException e) { + System.out.println("Decompilation canceled"); + } } @SuppressWarnings("UseOfSystemOutOrSystemErr") diff --git a/src/org/jetbrains/java/decompiler/main/plugins/PluginContext.java b/src/org/jetbrains/java/decompiler/main/plugins/PluginContext.java index 074be4b921..3b5591de3b 100644 --- a/src/org/jetbrains/java/decompiler/main/plugins/PluginContext.java +++ b/src/org/jetbrains/java/decompiler/main/plugins/PluginContext.java @@ -7,6 +7,7 @@ import org.jetbrains.java.decompiler.api.plugin.pass.NamedPass; import org.jetbrains.java.decompiler.api.plugin.pass.PassContext; import org.jetbrains.java.decompiler.main.DecompilerContext; +import org.jetbrains.java.decompiler.main.decompiler.CancelationManager; import org.jetbrains.java.decompiler.struct.StructClass; import java.util.*; @@ -54,6 +55,7 @@ public boolean runPasses(JavaPassLocation location, PassContext ctx) { List passes = this.passes.getOrDefault(location, Collections.emptyList()); for (NamedPass pass : passes) { + CancelationManager.checkCanceled(); if (pass.run(ctx) && location.isLoop()) { return true; } diff --git a/src/org/jetbrains/java/decompiler/main/rels/ClassWrapper.java b/src/org/jetbrains/java/decompiler/main/rels/ClassWrapper.java index cbe98d75b8..a4e183ff77 100644 --- a/src/org/jetbrains/java/decompiler/main/rels/ClassWrapper.java +++ b/src/org/jetbrains/java/decompiler/main/rels/ClassWrapper.java @@ -7,6 +7,7 @@ import org.jetbrains.java.decompiler.main.DecompilerContext; import org.jetbrains.java.decompiler.main.collectors.CounterContainer; import org.jetbrains.java.decompiler.main.collectors.VarNamesCollector; +import org.jetbrains.java.decompiler.main.decompiler.CancelationManager; import org.jetbrains.java.decompiler.main.extern.IFernflowerLogger; import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences; import org.jetbrains.java.decompiler.modules.decompiler.exps.Exprent; @@ -137,6 +138,9 @@ public void init(LanguageSpec spec) { } } } + catch (CancelationManager.CanceledException e) { + throw e; + } catch (Throwable t) { String message = "Method " + mt.getName() + " " + mt.getDescriptor() + " in class " + classStruct.qualifiedName + " couldn't be decompiled."; DecompilerContext.getLogger().writeMessage(message, IFernflowerLogger.Severity.WARN, t); diff --git a/src/org/jetbrains/java/decompiler/main/rels/MethodProcessor.java b/src/org/jetbrains/java/decompiler/main/rels/MethodProcessor.java index ecabae4286..aca0e3dec1 100644 --- a/src/org/jetbrains/java/decompiler/main/rels/MethodProcessor.java +++ b/src/org/jetbrains/java/decompiler/main/rels/MethodProcessor.java @@ -9,6 +9,7 @@ import org.jetbrains.java.decompiler.code.cfg.ControlFlowGraph; import org.jetbrains.java.decompiler.main.DecompilerContext; import org.jetbrains.java.decompiler.main.collectors.CounterContainer; +import org.jetbrains.java.decompiler.main.decompiler.CancelationManager; import org.jetbrains.java.decompiler.main.extern.IFernflowerLogger; import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences; import org.jetbrains.java.decompiler.main.plugins.PluginContext; @@ -67,6 +68,9 @@ public void run() { DecompilerContext.setCurrentContext(parentContext); root = codeToJava(klass, method, methodDescriptor, varProc, spec); } + catch (CancelationManager.CanceledException e) { + throw e; + } catch (Throwable t) { error = t; } @@ -81,6 +85,8 @@ public void run() { } public static RootStatement codeToJava(StructClass cl, StructMethod mt, MethodDescriptor md, VarProcessor varProc, LanguageSpec spec) throws IOException { + CancelationManager.checkCanceled(); + debugCurrentlyDecompiling.set(null); debugCurrentCFG.set(null); debugCurrentDecompileRecord.set(null); diff --git a/src/org/jetbrains/java/decompiler/struct/ContextUnit.java b/src/org/jetbrains/java/decompiler/struct/ContextUnit.java index b949922a76..0f004ff0d2 100644 --- a/src/org/jetbrains/java/decompiler/struct/ContextUnit.java +++ b/src/org/jetbrains/java/decompiler/struct/ContextUnit.java @@ -3,6 +3,7 @@ import org.jetbrains.java.decompiler.main.ClassWriter; import org.jetbrains.java.decompiler.main.DecompilerContext; +import org.jetbrains.java.decompiler.main.decompiler.CancelationManager; import org.jetbrains.java.decompiler.main.extern.IContextSource; import org.jetbrains.java.decompiler.main.extern.IFernflowerLogger; import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences; @@ -223,7 +224,13 @@ private static void waitForAll(final List> futures) { for (Future future : futures) { try { future.get(); - } catch (InterruptedException | ExecutionException e) { + } catch (ExecutionException e) { + if (e.getCause() instanceof CancelationManager.CanceledException) { + throw (CancelationManager.CanceledException) e.getCause(); + } else { + throw new RuntimeException(e); + } + } catch (InterruptedException e) { throw new RuntimeException(e); } } diff --git a/src/org/jetbrains/java/decompiler/struct/gen/generics/GenericMain.java b/src/org/jetbrains/java/decompiler/struct/gen/generics/GenericMain.java index 5a128724fa..f5dff1cd0d 100644 --- a/src/org/jetbrains/java/decompiler/struct/gen/generics/GenericMain.java +++ b/src/org/jetbrains/java/decompiler/struct/gen/generics/GenericMain.java @@ -3,6 +3,7 @@ import org.jetbrains.java.decompiler.code.CodeConstants; import org.jetbrains.java.decompiler.main.DecompilerContext; +import org.jetbrains.java.decompiler.main.decompiler.CancelationManager; import org.jetbrains.java.decompiler.main.extern.IFernflowerLogger; import org.jetbrains.java.decompiler.util.TextUtil; import org.jetbrains.java.decompiler.struct.gen.VarType; @@ -50,6 +51,9 @@ public static GenericClassDescriptor parseClassSignature(String qualifiedName, S return descriptor; } + catch (CancelationManager.CanceledException e) { + throw e; + } catch (RuntimeException e) { DecompilerContext.getLogger().writeMessage("Invalid signature: " + original, IFernflowerLogger.Severity.WARN); return null; @@ -60,6 +64,9 @@ public static GenericFieldDescriptor parseFieldSignature(String signature) { try { return new GenericFieldDescriptor(GenericType.parse(signature)); } + catch (CancelationManager.CanceledException e) { + throw e; + } catch (RuntimeException e) { DecompilerContext.getLogger().writeMessage("Invalid signature: " + signature, IFernflowerLogger.Severity.WARN); return null; @@ -98,6 +105,9 @@ public static GenericMethodDescriptor parseMethodSignature(String signature) { return new GenericMethodDescriptor(typeParameters, typeParameterBounds, parameterTypes, returnType, exceptionTypes); } + catch (CancelationManager.CanceledException e) { + throw e; + } catch (RuntimeException e) { DecompilerContext.getLogger().writeMessage("Invalid signature: " + original, IFernflowerLogger.Severity.WARN); return null;