Skip to content

Node-API-JSI: rethrow native errors from host functions as their JS value - #252

Open
matthargett wants to merge 2 commits into
BabylonJS:mainfrom
rebeckerspecialties:jsi-host-function-errors
Open

matthargett wants to merge 2 commits into
BabylonJS:mainfrom
rebeckerspecialties:jsi-host-function-errors

Conversation

@matthargett

@matthargett matthargett commented Sep 17, 2026

Copy link
Copy Markdown

Problem

On the JSI backend, an error thrown by native code — every Napi::TypeError/RangeError/Error a polyfill raises — reaches scripts as a generic Error("Exception in HostFunction: <message>"): JSI wraps any std::exception escaping a host function that way. e instanceof TypeError is false and e.message carries the prefix, unlike every other backend. #251's twin hit exactly this (expected [Function] to throw TypeError but Error: Exception in HostFunction: URL.createObjectURL: argument is not a Blob was thrown), and tests.ts already carries a comment working around the prefix.

Change

Every host-function trampoline in Core/Node-API-JSI/Include/napi/napi-inl.h (Function::New callbacks, class constructors, static/instance methods and accessors) now runs through details::CallHost, which catches Napi::Error and rethrows jsi::JSError with the original JS value — the same conversion Napi::Eval already performs in the other direction (#246). JSI propagates a JSError's value unchanged, so scripts observe the same exception on JSI as elsewhere.

Regression test (native exceptions suite): a TypeError from a static method and one from a constructor arrive as TypeError with their message and no HostFunction prefix. Passes on JavaScriptCore locally; the fork twin exercises the JSI matrix. Fork twin: rebeckerspecialties#36.

…alue

Native code throws Napi::Error, a C++ exception carrying the JavaScript
error object. JSI reports any std::exception that escapes a host
function as a fresh Error("Exception in HostFunction: " + what()), so
on the JSI backend a polyfill's TypeError reached scripts as a plain
Error with a prefixed message: `instanceof TypeError` was false and
messages no longer compared equal, unlike every other backend.

Route every host-function trampoline (Function::New callbacks, class
constructors, static and instance methods and accessors) through a
helper that catches Napi::Error and rethrows jsi::JSError with the
original value, the same conversion Napi::Eval already does in the
other direction (BabylonJS#246).

Regression test: "native exceptions reach scripts as the thrown error
object, with its class and message, on every engine".

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

JS-to-native callbacks can report an error either by throwing Napi::Error or by calling ThrowAsJavaScriptException, which records an exception in the environment. The JSI trampoline converted only the first form, allowing pending exceptions to escape their JavaScript call site and surface later as uncaught dispatcher failures.\n\nCheck and clear the pending slot before every JSI host callback returns, then throw its original JavaScript value through JSI. Add a reentrant callback regression that exercises the pending-exception form without depending on a particular polyfill.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants