Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/jsc/bindings/napi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3058,7 +3058,7 @@ extern "C" napi_status napi_run_script(napi_env env, napi_value script,
JSValue value = JSC::evaluate(globalObject, sourceCode, globalObject->globalThis(), returnedException);

if (returnedException) {
env->scheduleException(returnedException.get());
env->scheduleException(returnedException->value());
return napi_set_last_error(env, napi_generic_failure);
}

Expand Down
40 changes: 40 additions & 0 deletions test/napi/napi-app/standalone_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -724,6 +724,45 @@ static napi_value test_napi_run_script(const Napi::CallbackInfo &info) {
return ret;
}

// napi_run_script of a script that throws: the value returned by
// napi_get_and_clear_last_exception must be the thrown JS value, usable with
// napi_typeof and property access.
static napi_value
test_napi_run_script_exception_value(const Napi::CallbackInfo &info) {
napi_env env = info.Env();
// info[0] is the GC callback
napi_value script = info[1];

napi_value ret = nullptr;
napi_status run_status = napi_run_script(env, script, &ret);
bool pending = false;
NODE_API_CALL(env, napi_is_exception_pending(env, &pending));
printf("run status=%d pending=%d\n", (int)run_status, (int)pending);

napi_value exc = nullptr;
NODE_API_CALL(env, napi_get_and_clear_last_exception(env, &exc));

napi_valuetype type = (napi_valuetype)99;
napi_status typeof_status = napi_typeof(env, exc, &type);
bool is_error = false;
NODE_API_CALL(env, napi_is_error(env, exc, &is_error));
printf("typeof status=%d type=%d is_error=%d\n", (int)typeof_status,
(int)type, (int)is_error);

if (type == napi_object) {
napi_value message = nullptr;
NODE_API_CALL(env,
napi_get_named_property(env, exc, "message", &message));
char buf[128] = {0};
size_t len = 0;
NODE_API_CALL(env, napi_get_value_string_utf8(env, message, buf,
sizeof buf, &len));
printf("message=%s\n", buf);
}

return exc;
}

static napi_value test_napi_throw_with_nullptr(const Napi::CallbackInfo &info) {
napi_env env = info.Env();
const napi_status status = napi_throw(env, nullptr);
Expand Down Expand Up @@ -4623,6 +4662,7 @@ void register_standalone_tests(Napi::Env env, Napi::Object exports) {
REGISTER_FUNCTION(env, exports, test_napi_handle_scope_many_args);
REGISTER_FUNCTION(env, exports, test_napi_ref);
REGISTER_FUNCTION(env, exports, test_napi_run_script);
REGISTER_FUNCTION(env, exports, test_napi_run_script_exception_value);
REGISTER_FUNCTION(env, exports, test_napi_throw_with_nullptr);
REGISTER_FUNCTION(env, exports, test_extended_error_messages);
REGISTER_FUNCTION(env, exports, bigint_to_i64);
Expand Down
19 changes: 19 additions & 0 deletions test/napi/napi.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1001,6 +1001,25 @@ describe.concurrent.skipIf(!canBuildNodeAddons())("napi", () => {
it("propagates exceptions", async () => {
await checkSameOutput("test_napi_run_script", ["(()=>{ throw new TypeError('oops'); })()"]);
});
it("hands the thrown value to napi_get_and_clear_last_exception", async () => {
const result = await checkSameOutput("test_napi_run_script_exception_value", ['throw new RangeError("boom")']);
expect(result).toContain("run status=9 pending=1");
expect(result).toContain("typeof status=0 type=6 is_error=1");
expect(result).toContain("message=boom");
});
it("hands a syntax error to napi_get_and_clear_last_exception", async () => {
// V8 and JSC word the SyntaxError message differently, so only bun's output is checked
const result = await runOn(bunExe(), "test_napi_run_script_exception_value", ["1+"]);
expect(result).toContain("run status=9 pending=1");
expect(result).toContain("typeof status=0 type=6 is_error=1");
expect(result).toContain('synchronously threw Error: message "SyntaxError:');
});
it("hands a thrown primitive to napi_get_and_clear_last_exception", async () => {
const result = await checkSameOutput("test_napi_run_script_exception_value", ["throw 42"]);
expect(result).toContain("run status=9 pending=1");
expect(result).toContain("typeof status=0 type=3 is_error=0");
expect(result).toContain('synchronously threw Error: message "42"');
});
it("cannot see locals from around its invocation", async () => {
// variable should_not_exist is declared on main.js:18, but it should not be in scope for the eval'd code
// this doesn't use await checkSameOutput because V8 and JSC use different error messages for a missing variable
Expand Down