mirror of
https://github.com/enso-org/enso.git
synced 2025-01-03 14:42:21 +03:00
System.exit does proper context hard exit. (#10363)
The `System.exit 42` component is treated the same way as any other Panic error - it does not interfere with other component evaluation: ![image](https://github.com/user-attachments/assets/516490b5-755f-453e-8dc9-744437dc51bd) After removing the `System.exit 42` component, the workflow works as expected. I have also tried opening the project with the component and then removing it.
This commit is contained in:
parent
c20eab2af9
commit
451d7cb452
@ -873,10 +873,14 @@ public final class Main {
|
||||
}
|
||||
}
|
||||
} catch (PolyglotException e) {
|
||||
if (e.isExit()) {
|
||||
doExit(e.getExitStatus());
|
||||
} else {
|
||||
printPolyglotException(e, rootPkgPath);
|
||||
throw exitFail();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the `--repl` CLI option
|
||||
|
@ -3,10 +3,12 @@ package org.enso.interpreter.service;
|
||||
import com.oracle.truffle.api.CallTarget;
|
||||
import com.oracle.truffle.api.CompilerDirectives;
|
||||
import com.oracle.truffle.api.TruffleLogger;
|
||||
import com.oracle.truffle.api.exception.AbstractTruffleException;
|
||||
import com.oracle.truffle.api.frame.VirtualFrame;
|
||||
import com.oracle.truffle.api.instrumentation.EventBinding;
|
||||
import com.oracle.truffle.api.instrumentation.ExecutionEventNodeFactory;
|
||||
import com.oracle.truffle.api.interop.ArityException;
|
||||
import com.oracle.truffle.api.interop.ExceptionType;
|
||||
import com.oracle.truffle.api.interop.InteropLibrary;
|
||||
import com.oracle.truffle.api.interop.UnknownIdentifierException;
|
||||
import com.oracle.truffle.api.interop.UnsupportedMessageException;
|
||||
@ -479,6 +481,18 @@ public final class ExecutionService {
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean isExitException(AbstractTruffleException ex) {
|
||||
var interop = InteropLibrary.getUncached();
|
||||
if (interop.isException(ex)) {
|
||||
try {
|
||||
return interop.getExceptionType(ex) == ExceptionType.EXIT;
|
||||
} catch (UnsupportedMessageException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a human-readable message for a panic exception.
|
||||
*
|
||||
|
@ -49,7 +49,6 @@ import java.io.File
|
||||
import java.util.UUID
|
||||
import java.util.function.Consumer
|
||||
import java.util.logging.Level
|
||||
|
||||
import scala.jdk.OptionConverters.RichOptional
|
||||
import scala.util.Try
|
||||
|
||||
@ -324,9 +323,11 @@ object ProgramExecutionSupport {
|
||||
ctx: RuntimeContext
|
||||
): PartialFunction[Throwable, Api.ExecutionResult.Diagnostic] = {
|
||||
case ex: AbstractTruffleException
|
||||
// exit exception is special, and handled as failure rather than Diagnostics.
|
||||
if !ctx.executionService.isExitException(ex) &&
|
||||
// The empty language is allowed because `getLanguage` returns null when
|
||||
// the error originates in builtin node.
|
||||
if Option(ctx.executionService.getLanguage(ex))
|
||||
Option(ctx.executionService.getLanguage(ex))
|
||||
.forall(_ == LanguageInfo.ID) =>
|
||||
val section = Option(ctx.executionService.getSourceLocation(ex))
|
||||
val source = section.flatMap(sec => Option(sec.getSource))
|
||||
@ -357,6 +358,16 @@ object ProgramExecutionSupport {
|
||||
findFileByModuleName(ex.getModule)
|
||||
)
|
||||
|
||||
case exitEx: AbstractTruffleException
|
||||
if ctx.executionService.isExitException(exitEx) =>
|
||||
val section = Option(ctx.executionService.getSourceLocation(exitEx))
|
||||
val source = section.flatMap(sec => Option(sec.getSource))
|
||||
val file = source.flatMap(src => findFileByModuleName(src.getName))
|
||||
Api.ExecutionResult.Failure(
|
||||
exitEx.getMessage,
|
||||
file
|
||||
)
|
||||
|
||||
case ex: ServiceException =>
|
||||
Api.ExecutionResult.Failure(ex.getMessage, None)
|
||||
}
|
||||
|
@ -5773,6 +5773,62 @@ class RuntimeServerTest
|
||||
)
|
||||
}
|
||||
|
||||
it should "return error when invoking System.exit" in {
|
||||
val contextId = UUID.randomUUID()
|
||||
val requestId = UUID.randomUUID()
|
||||
val moduleName = "Enso_Test.Test.Main"
|
||||
val metadata = new Metadata
|
||||
val code =
|
||||
"""import Standard.Base.System
|
||||
|
|
||||
|main =
|
||||
| System.exit 42
|
||||
|""".stripMargin.linesIterator.mkString("\n")
|
||||
val contents = metadata.appendToCode(code)
|
||||
val mainFile = context.writeMain(contents)
|
||||
|
||||
// create context
|
||||
context.send(Api.Request(requestId, Api.CreateContextRequest(contextId)))
|
||||
context.receive shouldEqual Some(
|
||||
Api.Response(requestId, Api.CreateContextResponse(contextId))
|
||||
)
|
||||
|
||||
// Set sources for the module
|
||||
context.send(
|
||||
Api.Request(requestId, Api.OpenFileRequest(mainFile, contents))
|
||||
)
|
||||
context.receive shouldEqual Some(
|
||||
Api.Response(Some(requestId), Api.OpenFileResponse)
|
||||
)
|
||||
|
||||
// push main
|
||||
context.send(
|
||||
Api.Request(
|
||||
requestId,
|
||||
Api.PushContextRequest(
|
||||
contextId,
|
||||
Api.StackItem.ExplicitCall(
|
||||
Api.MethodPointer(moduleName, moduleName, "main"),
|
||||
None,
|
||||
Vector()
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
context.receiveNIgnoreStdLib(2) should contain theSameElementsAs Seq(
|
||||
Api.Response(requestId, Api.PushContextResponse(contextId)),
|
||||
Api.Response(
|
||||
Api.ExecutionFailed(
|
||||
contextId,
|
||||
Api.ExecutionResult.Failure(
|
||||
"Exit was called with exit code 42.",
|
||||
Some(mainFile)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
it should "return compiler warning unused variable" in {
|
||||
val contextId = UUID.randomUUID()
|
||||
val requestId = UUID.randomUUID()
|
||||
|
@ -0,0 +1,70 @@
|
||||
package org.enso.interpreter.runtime.system;
|
||||
|
||||
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
|
||||
import com.oracle.truffle.api.TruffleStackTraceElement;
|
||||
import com.oracle.truffle.api.exception.AbstractTruffleException;
|
||||
import com.oracle.truffle.api.interop.ExceptionType;
|
||||
import com.oracle.truffle.api.interop.InteropLibrary;
|
||||
import com.oracle.truffle.api.library.ExportLibrary;
|
||||
import com.oracle.truffle.api.library.ExportMessage;
|
||||
import com.oracle.truffle.api.nodes.Node;
|
||||
import org.enso.interpreter.runtime.data.vector.ArrayLikeHelpers;
|
||||
|
||||
/** Thrown when {@code System.exit} call was called during execution. */
|
||||
@ExportLibrary(InteropLibrary.class)
|
||||
final class ExitException extends AbstractTruffleException {
|
||||
private final int exitCode;
|
||||
|
||||
ExitException(int code, Node location) {
|
||||
super(location);
|
||||
this.exitCode = code;
|
||||
}
|
||||
|
||||
@ExportMessage
|
||||
boolean isException() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@ExportMessage
|
||||
int getExceptionExitStatus() {
|
||||
return exitCode;
|
||||
}
|
||||
|
||||
@ExportMessage
|
||||
ExceptionType getExceptionType() {
|
||||
return ExceptionType.EXIT;
|
||||
}
|
||||
|
||||
@ExportMessage
|
||||
boolean hasExceptionMessage() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@ExportMessage
|
||||
String getExceptionMessage() {
|
||||
return getMessage();
|
||||
}
|
||||
|
||||
@TruffleBoundary
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return "Exit was called with exit code " + exitCode + ".";
|
||||
}
|
||||
|
||||
@ExportMessage
|
||||
RuntimeException throwException() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@ExportMessage
|
||||
boolean hasExceptionStackTrace() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@ExportMessage
|
||||
Object getExceptionStackTrace() {
|
||||
var node = this.getLocation();
|
||||
var frame = TruffleStackTraceElement.create(node, node.getRootNode().getCallTarget(), null);
|
||||
return ArrayLikeHelpers.asVectorWithCheckAt(frame.getGuestObject());
|
||||
}
|
||||
}
|
@ -10,7 +10,6 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.enso.interpreter.EnsoLanguage;
|
||||
import org.enso.interpreter.dsl.Builtin;
|
||||
import org.enso.interpreter.node.expression.builtin.text.util.ExpectStringNode;
|
||||
import org.enso.interpreter.runtime.EnsoContext;
|
||||
@ -46,14 +45,15 @@ public class System {
|
||||
return java.lang.System.nanoTime();
|
||||
}
|
||||
|
||||
@Builtin.Specialize
|
||||
@Builtin.Method(
|
||||
description = "Exits the process, returning the provided code.",
|
||||
autoRegister = false)
|
||||
@CompilerDirectives.TruffleBoundary
|
||||
public static void exit(long code) {
|
||||
var ctx = EnsoContext.get(null);
|
||||
EnsoLanguage.get(null).disposeContext(ctx);
|
||||
java.lang.System.exit((int) code);
|
||||
public static void exit(long code, @Cached ExpectStringNode expectStringNode) {
|
||||
// expectStringNode is an artificial Node just to provide a location for
|
||||
// the exception
|
||||
throw new ExitException((int) code, expectStringNode);
|
||||
}
|
||||
|
||||
@Builtin.Specialize
|
||||
|
@ -3,3 +3,5 @@ create_process command arguments input redirect_in redirect_out redirect_err = @
|
||||
@Builtin_Type
|
||||
type System_Process_Result
|
||||
Result exit_code stdout stderr
|
||||
|
||||
exit code = @Builtin_Method "System.exit"
|
||||
|
Loading…
Reference in New Issue
Block a user