From 4b2afaec5686a9be40e33d80c0f8609ea730beae Mon Sep 17 00:00:00 2001 From: Dmitry Bushev Date: Thu, 19 Sep 2024 17:35:48 +0100 Subject: [PATCH 1/6] test: panic catch --- .../test/instrument/RuntimeErrorsTest.scala | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/RuntimeErrorsTest.scala b/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/RuntimeErrorsTest.scala index a890e8079b6c..d9a65230bd60 100644 --- a/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/RuntimeErrorsTest.scala +++ b/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/RuntimeErrorsTest.scala @@ -714,6 +714,86 @@ class RuntimeErrorsTest context.consumeOut shouldEqual Seq("42") } + it should "catch panic when instrumented" in { + val contextId = UUID.randomUUID() + val requestId = UUID.randomUUID() + val moduleName = "Enso_Test.Test.Main" + val metadata = new Metadata + val catchId = metadata.addItem(46, 42, "aa") // Panic.catch Any ... + val throwId = metadata.addItem(63, 14, "ab") // (Panic.throw 42) + + val code = + """from Standard.Base import all + | + |main = + | x = Panic.catch Any (Panic.throw 42) _.payload + | IO.println x + |""".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)) + ) + + // Open the new file + 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, "Enso_Test.Test.Main", "main"), + None, + Vector() + ) + ) + ) + ) + context.receiveNIgnorePendingExpressionUpdates( + 4 + ) should contain theSameElementsAs Seq( + Api.Response(requestId, Api.PushContextResponse(contextId)), + TestMessages.panic( + contextId, + throwId, + Api.MethodCall( + Api.MethodPointer( + "Standard.Base.Panic", + "Standard.Base.Panic.Panic", + "throw" + ) + ), + Api.ExpressionUpdate.Payload.Panic("Integer", Seq(throwId, xId)), + builtin = false + ), + TestMessages.update( + contextId, + catchId, + ConstantsGen.INTEGER, + Api.MethodCall( + Api.MethodPointer( + "Standard.Base.Panic", + "Standard.Base.Panic.Panic", + "catch" + ) + ) + ), + context.executionComplete(contextId) + ) + context.consumeOut shouldEqual Seq("42") + } + it should "return dataflow errors continuing execution" in { val contextId = UUID.randomUUID() val requestId = UUID.randomUUID() From 63350f11b6924416b85f59c62c24171f857057c8 Mon Sep 17 00:00:00 2001 From: Dmitry Bushev Date: Wed, 25 Sep 2024 09:32:07 +0100 Subject: [PATCH 2/6] fix: typo --- .../enso/interpreter/test/instrument/RuntimeErrorsTest.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/RuntimeErrorsTest.scala b/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/RuntimeErrorsTest.scala index d9a65230bd60..ebdce8525f9d 100644 --- a/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/RuntimeErrorsTest.scala +++ b/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/RuntimeErrorsTest.scala @@ -774,7 +774,7 @@ class RuntimeErrorsTest "throw" ) ), - Api.ExpressionUpdate.Payload.Panic("Integer", Seq(throwId, xId)), + Api.ExpressionUpdate.Payload.Panic("Integer", Seq(throwId, catchId)), builtin = false ), TestMessages.update( From b89275bf2cb02b3ad9f1a64db2cf90f87beb87c6 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Wed, 25 Sep 2024 10:56:53 +0200 Subject: [PATCH 3/6] Fix for the catch panic when instrumented RuntimeErrorsTest --- .../node/expression/builtin/error/CatchPanicNode.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/error/CatchPanicNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/error/CatchPanicNode.java index 53a716f4fb51..6d29469fad89 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/error/CatchPanicNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/error/CatchPanicNode.java @@ -20,6 +20,7 @@ import org.enso.interpreter.runtime.callable.argument.CallArgumentInfo; import org.enso.interpreter.runtime.data.atom.AtomNewInstanceNode; import org.enso.interpreter.runtime.error.PanicException; +import org.enso.interpreter.runtime.error.PanicSentinel; import org.enso.interpreter.runtime.state.State; @BuiltinMethod( @@ -61,7 +62,11 @@ Object doExecute( @CachedLibrary(limit = "3") InteropLibrary interop) { try { // Note [Tail call] - return thunkExecutorNode.executeThunk(frame, action, state, BaseNode.TailStatus.NOT_TAIL); + var ret = thunkExecutorNode.executeThunk(frame, action, state, BaseNode.TailStatus.NOT_TAIL); + if (ret instanceof PanicSentinel sentinel) { + throw sentinel.getPanic(); + } + return ret; } catch (PanicException e) { panicBranchProfile.enter(); Object payload = e.getPayload(); From a68638785fa31e8a6e57aac995f84829b39709cf Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Wed, 25 Sep 2024 10:59:28 +0200 Subject: [PATCH 4/6] Using metadata.assertInCode --- .../test/instrument/RuntimeErrorsTest.scala | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/RuntimeErrorsTest.scala b/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/RuntimeErrorsTest.scala index ebdce8525f9d..880eaea880dc 100644 --- a/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/RuntimeErrorsTest.scala +++ b/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/RuntimeErrorsTest.scala @@ -719,8 +719,8 @@ class RuntimeErrorsTest val requestId = UUID.randomUUID() val moduleName = "Enso_Test.Test.Main" val metadata = new Metadata - val catchId = metadata.addItem(46, 42, "aa") // Panic.catch Any ... - val throwId = metadata.addItem(63, 14, "ab") // (Panic.throw 42) + val catchId = metadata.addItem(46, 42, "aa") + val throwId = metadata.addItem(63, 14, "ab") val code = """from Standard.Base import all @@ -732,6 +732,13 @@ class RuntimeErrorsTest val contents = metadata.appendToCode(code) val mainFile = context.writeMain(contents) + metadata.assertInCode( + catchId, + contents, + "Panic.catch Any (Panic.throw 42) _.payload" + ) + metadata.assertInCode(throwId, contents, "Panic.throw 42") + // create context context.send(Api.Request(requestId, Api.CreateContextRequest(contextId))) context.receive shouldEqual Some( From 3fb27c98a2dbd21c4cd94fc961de4e1720f903fe Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Wed, 25 Sep 2024 13:47:24 +0200 Subject: [PATCH 5/6] Testing behavior of CatchPanicNode --- .../builtin/error/CatchPanicNodeTest.java | 218 ++++++++++++++++++ 1 file changed, 218 insertions(+) create mode 100644 engine/runtime-integration-tests/src/test/java/org/enso/interpreter/node/expression/builtin/error/CatchPanicNodeTest.java diff --git a/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/node/expression/builtin/error/CatchPanicNodeTest.java b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/node/expression/builtin/error/CatchPanicNodeTest.java new file mode 100644 index 000000000000..86c5128f61da --- /dev/null +++ b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/node/expression/builtin/error/CatchPanicNodeTest.java @@ -0,0 +1,218 @@ +package org.enso.interpreter.node.expression.builtin.error; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import com.oracle.truffle.api.interop.InteropLibrary; +import org.enso.interpreter.node.expression.builtin.interop.syntax.HostValueToEnsoNode; +import org.enso.interpreter.runtime.EnsoContext; +import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition; +import org.enso.interpreter.runtime.callable.function.Function; +import org.enso.interpreter.runtime.callable.function.FunctionSchema; +import org.enso.interpreter.runtime.data.text.Text; +import org.enso.interpreter.runtime.error.PanicException; +import org.enso.interpreter.runtime.library.dispatch.TypeOfNode; +import org.enso.test.utils.ContextUtils; +import org.enso.test.utils.TestRootNode; +import org.graalvm.polyglot.Context; +import org.hamcrest.Matchers; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +public class CatchPanicNodeTest { + + private static final InteropLibrary interop = InteropLibrary.getUncached(); + + private static Context context; + private static CatchPanicNode catchPanicNode; + private static HostValueToEnsoNode hostValueToEnsoNode; + private static TestRootNode testRootNode; + + @BeforeClass + public static void initContextAndData() { + context = ContextUtils.createDefaultContext(); + ContextUtils.executeInContext( + context, + () -> { + catchPanicNode = CatchPanicNode.build(); + hostValueToEnsoNode = HostValueToEnsoNode.build(); + testRootNode = new TestRootNode(); + testRootNode.insertChildren(catchPanicNode, hostValueToEnsoNode); + return null; + }); + } + + @AfterClass + public static void disposeContext() { + context.close(); + context = null; + } + + @Test + public void passNothingThru() throws Exception { + ContextUtils.executeInContext( + context, + () -> { + var ctx = EnsoContext.get(catchPanicNode); + var any = ctx.getBuiltins().any(); + var nothing = ctx.getBuiltins().nothing(); + var result = catchPanicNode.execute(null, null, any, nothing, null); + assertEquals("Nothing gets returned", nothing, result); + return null; + }); + } + + @Test + public void passTextThru() throws Exception { + ContextUtils.executeInContext( + context, + () -> { + var ctx = EnsoContext.get(catchPanicNode); + var any = ctx.getBuiltins().any(); + var text = Text.create("Hello"); + var result = catchPanicNode.execute(null, null, any, text, null); + assertEquals("Hello gets returned", "Hello", result.toString()); + return null; + }); + } + + @Test + public void passEvaluatedThunkThru() throws Exception { + ContextUtils.executeInContext( + context, + () -> { + var ctx = EnsoContext.get(catchPanicNode); + var any = ctx.getBuiltins().any(); + var text = Text.create("Running"); + var fn = new TestRootNode((frame) -> text); + var thunk = Function.thunk(fn.getCallTarget(), null); + var result = catchPanicNode.execute(null, null, any, thunk, null); + assertEquals("Thunk gets evaluated", "Running", result.toString()); + return null; + }); + } + + @Test + public void catchAnyPanic() throws Exception { + ContextUtils.executeInContext( + context, + () -> { + var ctx = EnsoContext.get(catchPanicNode); + var any = ctx.getBuiltins().any(); + var thrown = Text.create("Thrown"); + var text = Text.create("Catched"); + var handlerFn = + new TestRootNode( + (frame) -> { + var args = + Function.ArgumentsHelper.getPositionalArguments(frame.getArguments()); + assertEquals("One argument expected", 1, args.length); + var argType = TypeOfNode.getUncached().execute(args[0]); + if (argType == ctx.getBuiltins().caughtPanic().getType()) { + assertThat(args[0].toString(), Matchers.containsString("Thrown")); + return text; + } else { + fail("Expecting Catched_Panic: " + args[0] + " type: " + argType); + return null; + } + }); + var fn = + new TestRootNode( + (frame) -> { + throw new PanicException(thrown, null); + }); + var thunk = Function.thunk(fn.getCallTarget(), null); + var handler = new Function(handlerFn.getCallTarget(), null, schema("err")); + var result = catchPanicNode.execute(null, null, any, thunk, handler); + assertEquals("Thunk gets evaluated", "Catched", result.toString()); + return null; + }); + } + + @Test + public void catchSpecificPanic() throws Exception { + ContextUtils.executeInContext( + context, + () -> { + var ctx = EnsoContext.get(catchPanicNode); + var textType = ctx.getBuiltins().text(); + var thrown = Text.create("Thrown"); + var text = Text.create("Catched"); + var handlerFn = + new TestRootNode( + (frame) -> { + var args = + Function.ArgumentsHelper.getPositionalArguments(frame.getArguments()); + assertEquals("One argument expected", 1, args.length); + var argType = TypeOfNode.getUncached().execute(args[0]); + if (argType == ctx.getBuiltins().caughtPanic().getType()) { + assertThat(args[0].toString(), Matchers.containsString("Thrown")); + return text; + } else { + fail("Expecting Catched_Panic: " + args[0] + " type: " + argType); + return null; + } + }); + var fn = + new TestRootNode( + (frame) -> { + throw new PanicException(thrown, null); + }); + var thunk = Function.thunk(fn.getCallTarget(), null); + var handler = new Function(handlerFn.getCallTarget(), null, schema("err")); + var result = catchPanicNode.execute(null, null, textType, thunk, handler); + assertEquals("Thunk gets evaluated", "Catched", result.toString()); + return null; + }); + } + + @Test + public void dontCatchSpecificPanic() throws Exception { + ContextUtils.executeInContext( + context, + () -> { + var ctx = EnsoContext.get(catchPanicNode); + var numberType = ctx.getBuiltins().number().getNumber(); + var thrown = Text.create("Thrown"); + var text = Text.create("Catched"); + var handlerFn = + new TestRootNode( + (frame) -> { + var args = + Function.ArgumentsHelper.getPositionalArguments(frame.getArguments()); + assertEquals("One argument expected", 1, args.length); + var argType = TypeOfNode.getUncached().execute(args[0]); + if (argType == ctx.getBuiltins().caughtPanic().getType()) { + assertThat(args[0].toString(), Matchers.containsString("Thrown")); + return text; + } else { + fail("Expecting Catched_Panic: " + args[0] + " type: " + argType); + return null; + } + }); + var fn = + new TestRootNode( + (frame) -> { + throw new PanicException(thrown, null); + }); + var thunk = Function.thunk(fn.getCallTarget(), null); + var handler = new Function(handlerFn.getCallTarget(), null, schema("err")); + try { + var result = catchPanicNode.execute(null, null, numberType, thunk, handler); + fail("Not expecting any result back: " + result); + } catch (PanicException ex) { + // OK + assertEquals("Thrown", ex.getMessage()); + } + return null; + }); + } + + private static FunctionSchema schema(String argName) { + var def = + new ArgumentDefinition(0, argName, null, null, ArgumentDefinition.ExecutionMode.EXECUTE); + return FunctionSchema.newBuilder().argumentDefinitions(def).build(); + } +} From a85c561d2a902cbbe4a60ea1f837f034dcac0154 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Wed, 25 Sep 2024 13:55:19 +0200 Subject: [PATCH 6/6] PanicSentinel tests for CatchPanicNode --- .../builtin/error/CatchPanicNodeTest.java | 117 ++++++++++++++++++ 1 file changed, 117 insertions(+) diff --git a/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/node/expression/builtin/error/CatchPanicNodeTest.java b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/node/expression/builtin/error/CatchPanicNodeTest.java index 86c5128f61da..08c5f9c6d490 100644 --- a/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/node/expression/builtin/error/CatchPanicNodeTest.java +++ b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/node/expression/builtin/error/CatchPanicNodeTest.java @@ -12,6 +12,7 @@ import org.enso.interpreter.runtime.callable.function.FunctionSchema; import org.enso.interpreter.runtime.data.text.Text; import org.enso.interpreter.runtime.error.PanicException; +import org.enso.interpreter.runtime.error.PanicSentinel; import org.enso.interpreter.runtime.library.dispatch.TypeOfNode; import org.enso.test.utils.ContextUtils; import org.enso.test.utils.TestRootNode; @@ -131,6 +132,43 @@ public void catchAnyPanic() throws Exception { }); } + @Test + public void catchAnyPanicSentinel() throws Exception { + ContextUtils.executeInContext( + context, + () -> { + var ctx = EnsoContext.get(catchPanicNode); + var any = ctx.getBuiltins().any(); + var thrown = Text.create("Thrown"); + var text = Text.create("Catched"); + var handlerFn = + new TestRootNode( + (frame) -> { + var args = + Function.ArgumentsHelper.getPositionalArguments(frame.getArguments()); + assertEquals("One argument expected", 1, args.length); + var argType = TypeOfNode.getUncached().execute(args[0]); + if (argType == ctx.getBuiltins().caughtPanic().getType()) { + assertThat(args[0].toString(), Matchers.containsString("Thrown")); + return text; + } else { + fail("Expecting Catched_Panic: " + args[0] + " type: " + argType); + return null; + } + }); + var fn = + new TestRootNode( + (frame) -> { + return new PanicSentinel(new PanicException(thrown, null), null); + }); + var thunk = Function.thunk(fn.getCallTarget(), null); + var handler = new Function(handlerFn.getCallTarget(), null, schema("err")); + var result = catchPanicNode.execute(null, null, any, thunk, handler); + assertEquals("Thunk gets evaluated", "Catched", result.toString()); + return null; + }); + } + @Test public void catchSpecificPanic() throws Exception { ContextUtils.executeInContext( @@ -168,6 +206,43 @@ public void catchSpecificPanic() throws Exception { }); } + @Test + public void catchSpecificPanicSentinel() throws Exception { + ContextUtils.executeInContext( + context, + () -> { + var ctx = EnsoContext.get(catchPanicNode); + var textType = ctx.getBuiltins().text(); + var thrown = Text.create("Thrown"); + var text = Text.create("Catched"); + var handlerFn = + new TestRootNode( + (frame) -> { + var args = + Function.ArgumentsHelper.getPositionalArguments(frame.getArguments()); + assertEquals("One argument expected", 1, args.length); + var argType = TypeOfNode.getUncached().execute(args[0]); + if (argType == ctx.getBuiltins().caughtPanic().getType()) { + assertThat(args[0].toString(), Matchers.containsString("Thrown")); + return text; + } else { + fail("Expecting Catched_Panic: " + args[0] + " type: " + argType); + return null; + } + }); + var fn = + new TestRootNode( + (frame) -> { + return new PanicSentinel(new PanicException(thrown, null), null); + }); + var thunk = Function.thunk(fn.getCallTarget(), null); + var handler = new Function(handlerFn.getCallTarget(), null, schema("err")); + var result = catchPanicNode.execute(null, null, textType, thunk, handler); + assertEquals("Thunk gets evaluated", "Catched", result.toString()); + return null; + }); + } + @Test public void dontCatchSpecificPanic() throws Exception { ContextUtils.executeInContext( @@ -210,6 +285,48 @@ public void dontCatchSpecificPanic() throws Exception { }); } + @Test + public void dontCatchSpecificPanicSentinel() throws Exception { + ContextUtils.executeInContext( + context, + () -> { + var ctx = EnsoContext.get(catchPanicNode); + var numberType = ctx.getBuiltins().number().getNumber(); + var thrown = Text.create("Thrown"); + var text = Text.create("Catched"); + var handlerFn = + new TestRootNode( + (frame) -> { + var args = + Function.ArgumentsHelper.getPositionalArguments(frame.getArguments()); + assertEquals("One argument expected", 1, args.length); + var argType = TypeOfNode.getUncached().execute(args[0]); + if (argType == ctx.getBuiltins().caughtPanic().getType()) { + assertThat(args[0].toString(), Matchers.containsString("Thrown")); + return text; + } else { + fail("Expecting Catched_Panic: " + args[0] + " type: " + argType); + return null; + } + }); + var fn = + new TestRootNode( + (frame) -> { + return new PanicSentinel(new PanicException(thrown, null), null); + }); + var thunk = Function.thunk(fn.getCallTarget(), null); + var handler = new Function(handlerFn.getCallTarget(), null, schema("err")); + try { + var result = catchPanicNode.execute(null, null, numberType, thunk, handler); + fail("Not expecting any result back: " + result); + } catch (PanicException ex) { + // OK + assertEquals("Thrown", ex.getMessage()); + } + return null; + }); + } + private static FunctionSchema schema(String argName) { var def = new ArgumentDefinition(0, argName, null, null, ArgumentDefinition.ExecutionMode.EXECUTE);