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:
Pavel Marek 2024-07-18 20:10:36 +02:00 committed by GitHub
parent c20eab2af9
commit 451d7cb452
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 166 additions and 9 deletions

View File

@ -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

View File

@ -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.
*

View File

@ -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)
}

View File

@ -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()

View File

@ -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());
}
}

View File

@ -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

View File

@ -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"