oracle / graaljs

A ECMAScript 2023 compliant JavaScript implementation built on GraalVM. With polyglot language interoperability support. Running Node.js applications!
Universal Permissive License v1.0
1.76k stars 188 forks source link

Errors in Polyglot API are not propagated correctly in Node #185

Open fniephaus opened 5 years ago

fniephaus commented 5 years ago

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

iamstolis commented 5 years ago

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)
wirthi commented 3 years ago

Is this still open given the latest changes around TruffleException?

fniephaus commented 3 years ago

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