Errors in Polyglot API are not propagated correctly in Node
Describe the Bug
When using Polyglot.eval() from inside Graal-Node.js and an error occurs in either Python or Ruby as the execution language, the user sees a cryptic JS error message instead of the original error.
Steps to Reproduce
Python
$ node --jvm --polyglot
> result = Polyglot.eval('python', 'a + b')
TypeError: Cannot convert undefined or null to object: undefined
at Function.defineProperties (native)
...
Ruby
$ node --jvm --polyglot
> result = Polyglot.eval('ruby', 'a + b')
TypeError: Object prototype may only be an Object or null: DynamicObject@28fe2bef<Method>
at Function.setPrototypeOf (native)
...
Expected Behavior
An error native to the used execution language (Python or Ruby) should be shown signaling that the used variable a is not defined in the scope. See js for an example:
$ node --jvm --polyglot
> result = Polyglot.eval('js', 'a + b')
ReferenceError: a is not defined
at <eval>:1:1
at Object.eval (native)
Actual Behavior
A cryptic error message is shown.
Tested with GraalVM CE 19.0.0 on macOS.
/cc @jak-ing
The python version of the problem was fixed some time ago. The output from the latest GraalVM build is
$ ./node --jvm --polyglot
> result = Polyglot.eval('python', 'a + b')
Thrown: [Object: null prototype] {
args:
[Array: null prototype] [
'name \'a\' is not defined',
count: [Function],
index: [Function] ],
with_traceback: [Function] }
The output is not very nice but is shows the error message at least. You cannot realistically expect seeing ReferenceError here. ReferenceError is JavaScript-specific error and the displayed error comes from Python.
The ruby version is more complicated:
$ ./node --jvm --polyglot
> result = Polyglot.eval('ruby', 'a + b')
TypeError: Object prototype may only be an Object or null: Executable
at Function.setPrototypeOf (native)
at deprecate (internal/util.js:70:10)
at formatValue (internal/util/inspect.js:481:21)
at Object.inspect (internal/util/inspect.js:191:10)
at Domain.debugDomainError (repl.js:439:34)
at Domain.emit (events.js:189:13)
at Domain.emit (domain.js:441:20)
at REPLServer.defaultEval (repl.js:353:26)
at bound (domain.js:395:14)
at REPLServer.runBound (domain.js:408:12)
The error comes from the code that attempts to use custom inspection on the ruby error. ruby objects tend to have inspect() method and Node.js REPL attempts to call this method because it thinks that the method provides a useful information to display. This part of custom inspection is problematic and was deprecated. In fact, the error above is thrown by the code that attempts to mark the inspect method from ruby as deprecated (using util.deprecate). The root of the problem is that non-JavaScript objects cannot be set as prototypes of other objects.
Unfortunately, even if we support non-JavaScript prototypes then we don't get much further. We can simulate that by deactivating the deprecation that caused the previous problem:
$ ./node --jvm --polyglot --no-deprecation
> result = Polyglot.eval('ruby', 'a + b')
unknown:0
unknown
^
Error: wrong number of arguments (given 3, expected 0) (ArgumentError)
at formatValue (internal/util/inspect.js:493:31)
at Object.inspect (internal/util/inspect.js:191:10)
at Domain.debugDomainError (repl.js:439:34)
at Domain.emit (events.js:189:13)
at Domain.emit (domain.js:441:20)
at Domain._errorHandler (domain.js:223:23)
at Object.<anonymous> (domain.js:139:29)
at process._fatalException (internal/bootstrap/node.js:506:31)
Not we get to the actual call of the inspect() method. Unfortunately, util.inspect() attempts to call this method with 3 arguments and this method expects no arguments. So, it fails.
There is not much we can do with that. This is a Node.js feature/bug that may occur with regular JavaScript objects as well:
$ ./node --jvm --polyglot --no-deprecation
> ({ inspect: function() { if (arguments.length != 0) throw new Error("ArgumentError"); } })
Error: ArgumentError
at Object.inspect (repl:1:59)
at formatValue (internal/util/inspect.js:493:31)
at Object.inspect (internal/util/inspect.js:191:10)
Is this still open given the latest changes around TruffleException?
I re-ran the reproducers on a recent nightly 21.0.0-dev build and got this:
Python
$ node --jvm --polyglot
Welcome to Node.js v12.18.4.
Type ".help" for more information.
> result = Polyglot.eval('python', 'a + b')
Uncaught [[Object: null prototype]: Inspection interrupted prematurely. Maximum call stack size exceeded.]
Ruby
$ node --jvm --polyglot
Welcome to Node.js v12.18.4.
Type ".help" for more information.
> result = Polyglot.eval('ruby', 'a + b')
java.lang.ClassCastException: class org.truffleruby.language.objects.RubyObjectType cannot be cast to class com.oracle.truffle.js.runtime.builtins.JSClass (org.truffleruby.language.objects.RubyObjectType and com.oracle.truffle.js.runtime.builtins.JSClass are in unnamed module of loader com.oracle.graalvm.locator.GraalVMLocator$GuestLangToolsLoader @1ff8b8f)
at com.oracle.truffle.js.runtime.objects.JSShape.getJSClass(JSShape.java:94)
at com.oracle.truffle.js.runtime.objects.JSObject.getJSClass(JSObject.java:290)
at com.oracle.truffle.js.runtime.objects.JSObject.get(JSObject.java:321)
at com.oracle.truffle.trufflenode.GraalJSAccess.objectGetConstructorName(GraalJSAccess.java:938)
at com.oracle.truffle.trufflenode.NativeAccess.executeFunction1(Native Method)
at com.oracle.truffle.trufflenode.node.ExecuteNativeFunctionNode.executeFunction1(ExecuteNativeFunctionNode.java:284)
at com.oracle.truffle.trufflenode.node.ExecuteNativeFunctionNode.execute(ExecuteNativeFunctionNode.java:161)
at com.oracle.truffle.trufflenode.node.ExecuteNativeFunctionNode$NativeFunctionRootNode.execute(ExecuteNativeFunctionNode.java:343)
at jdk.internal.vm.compiler/org.graalvm.compiler.truffle.runtime.OptimizedCallTarget.executeRootNode(OptimizedCallTarget.java:555)
at jdk.internal.vm.compiler/org.graalvm.compiler.truffle.runtime.OptimizedCallTarget.profiledPERoot(OptimizedCallTarget.java:526)
at jdk.internal.vm.compiler/org.graalvm.compiler.truffle.runtime.OptimizedCallTarget.callBoundary(OptimizedCallTarget.java:480)
at jdk.internal.vm.compiler/org.graalvm.compiler.truffle.runtime.OptimizedCallTarget.doInvoke(OptimizedCallTarget.java:464)
at jdk.internal.vm.compiler/org.graalvm.compiler.truffle.runtime.OptimizedCallTarget.callDirect(OptimizedCallTarget.java:427)
at jdk.internal.vm.compiler/org.graalvm.compiler.truffle.runtime.OptimizedDirectCallNode.call(OptimizedDirectCallNode.java:71)
at com.oracle.truffle.js.nodes.function.JSFunctionCallNode$UnboundJSFunctionCacheNode.executeCall(JSFunctionCallNode.java:1265)
at com.oracle.truffle.js.nodes.function.JSFunctionCallNode.executeCall(JSFunctionCallNode.java:234)
at com.oracle.truffle.js.nodes.function.JSFunctionCallNode$CallNode.execute(JSFunctionCallNode.java:518)
at com.oracle.truffle.js.nodes.access.JSWriteCurrentFrameSlotNodeGen.execute_generic4(JSWriteCurrentFrameSlotNodeGen.java:154)
at com.oracle.truffle.js.nodes.access.JSWriteCurrentFrameSlotNodeGen.execute(JSWriteCurrentFrameSlotNodeGen.java:80)
at com.oracle.truffle.js.nodes.access.JSWriteCurrentFrameSlotNodeGen.executeVoid(JSWriteCurrentFrameSlotNodeGen.java:302)
at com.oracle.truffle.js.nodes.control.AbstractBlockNode.executeVoid(AbstractBlockNode.java:80)
at com.oracle.truffle.js.nodes.control.AbstractBlockNode.executeVoid(AbstractBlockNode.java:55)
at jdk.internal.vm.compiler/org.graalvm.compiler.truffle.runtime.OptimizedBlockNode.executeVoid(OptimizedBlockNode.java:120)
at com.oracle.truffle.js.nodes.control.AbstractBlockNode.executeVoid(AbstractBlockNode.java:70)
at com.oracle.truffle.js.nodes.control.IfNode.executeVoid(IfNode.java:178)
at com.oracle.truffle.js.nodes.control.AbstractBlockNode.executeVoid(AbstractBlockNode.java:80)
at com.oracle.truffle.js.nodes.control.AbstractBlockNode.executeVoid(AbstractBlockNode.java:55)
at jdk.internal.vm.compiler/org.graalvm.compiler.truffle.runtime.OptimizedBlockNode.executeGeneric(OptimizedBlockNode.java:79)
at com.oracle.truffle.js.nodes.control.AbstractBlockNode.execute(AbstractBlockNode.java:75)
at com.oracle.truffle.js.nodes.control.AbstractBlockNode.executeGeneric(AbstractBlockNode.java:85)
at com.oracle.truffle.js.nodes.control.AbstractBlockNode.executeGeneric(AbstractBlockNode.java:55)
at jdk.internal.vm.compiler/org.graalvm.compiler.truffle.runtime.OptimizedBlockNode.executeGeneric(OptimizedBlockNode.java:81)
at com.oracle.truffle.js.nodes.control.AbstractBlockNode.execute(AbstractBlockNode.java:75)
at com.oracle.truffle.js.nodes.function.FunctionBodyNode.execute(FunctionBodyNode.java:73)
at com.oracle.truffle.js.nodes.function.FunctionRootNode.executeInRealm(FunctionRootNode.java:147)
at com.oracle.truffle.js.runtime.JavaScriptRealmBoundaryRootNode.execute(JavaScriptRealmBoundaryRootNode.java:93)
at jdk.internal.vm.compiler/org.graalvm.compiler.truffle.runtime.OptimizedCallTarget.executeRootNode(OptimizedCallTarget.java:555)
at jdk.internal.vm.compiler/org.graalvm.compiler.truffle.runtime.OptimizedCallTarget.profiledPERoot(OptimizedCallTarget.java:526)
at jdk.internal.vm.compiler/org.graalvm.compiler.truffle.runtime.OptimizedCallTarget.callBoundary(OptimizedCallTarget.java:480)
at jdk.internal.vm.compiler/org.graalvm.compiler.truffle.runtime.OptimizedCallTarget.doInvoke(OptimizedCallTarget.java:464)
at jdk.internal.vm.compiler/org.graalvm.compiler.truffle.runtime.OptimizedCallTarget.callDirect(OptimizedCallTarget.java:427)
at jdk.internal.vm.compiler/org.graalvm.compiler.truffle.runtime.OptimizedDirectCallNode.call(OptimizedDirectCallNode.java:71)
at com.oracle.truffle.js.nodes.function.JSFunctionCallNode$UnboundJSFunctionCacheNode.executeCall(JSFunctionCallNode.java:1265)
at com.oracle.truffle.js.nodes.function.JSFunctionCallNode.executeAndSpecialize(JSFunctionCallNode.java:292)
at com.oracle.truffle.js.nodes.function.JSFunctionCallNode.executeCall(JSFunctionCallNode.java:238)
at com.oracle.truffle.js.nodes.function.JSFunctionCallNode$CallNode.execute(JSFunctionCallNode.java:518)
at com.oracle.truffle.js.nodes.access.PropertyNode.evaluateTarget(PropertyNode.java:186)
at com.oracle.truffle.js.nodes.function.JSFunctionCallNode$InvokeNode.executeTarget(JSFunctionCallNode.java:728)
at com.oracle.truffle.js.nodes.function.JSFunctionCallNode$InvokeNode.execute(JSFunctionCallNode.java:717)
at com.oracle.truffle.js.nodes.access.JSWriteCurrentFrameSlotNodeGen.execute_generic4(JSWriteCurrentFrameSlotNodeGen.java:154)
at com.oracle.truffle.js.nodes.access.JSWriteCurrentFrameSlotNodeGen.execute(JSWriteCurrentFrameSlotNodeGen.java:80)
at com.oracle.truffle.js.nodes.access.JSWriteCurrentFrameSlotNodeGen.executeVoid(JSWriteCurrentFrameSlotNodeGen.java:302)
at com.oracle.truffle.js.nodes.control.AbstractBlockNode.executeVoid(AbstractBlockNode.java:80
...