Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

error: do not replace pending exception #629

Closed
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
20 changes: 9 additions & 11 deletions napi-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -2037,28 +2037,26 @@ inline void Buffer<T>::EnsureInfo() const {
inline Error Error::New(napi_env env) {
napi_status status;
napi_value error = nullptr;

bool is_exception_pending;
const napi_extended_error_info* info;

// We must retrieve the last error info before doing anything else, because
// doing anything else will replace the last error info.
status = napi_get_last_error_info(env, &info);
NAPI_FATAL_IF_FAILED(status, "Error::New", "napi_get_last_error_info");

if (info->error_code == napi_pending_exception) {
status = napi_is_exception_pending(env, &is_exception_pending);
NAPI_FATAL_IF_FAILED(status, "Error::New", "napi_is_exception_pending");

// A pending exception takes precedence over any internal error status.
if (is_exception_pending) {
status = napi_get_and_clear_last_exception(env, &error);
NAPI_FATAL_IF_FAILED(status, "Error::New", "napi_get_and_clear_last_exception");
}
else {
const char* error_message = info->error_message != nullptr ?
info->error_message : "Error in native callback";

bool isExceptionPending;
status = napi_is_exception_pending(env, &isExceptionPending);
NAPI_FATAL_IF_FAILED(status, "Error::New", "napi_is_exception_pending");

if (isExceptionPending) {
status = napi_get_and_clear_last_exception(env, &error);
NAPI_FATAL_IF_FAILED(status, "Error::New", "napi_get_and_clear_last_exception");
}

napi_value message;
status = napi_create_string_utf8(
env,
Expand Down
35 changes: 35 additions & 0 deletions test/error.cc
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,40 @@ void ThrowFatalError(const CallbackInfo& /*info*/) {
Error::Fatal("Error::ThrowFatalError", "This is a fatal error");
}

void ThrowDefaultError(const CallbackInfo& info) {
napi_value dummy;
napi_env env = info.Env();
napi_status status = napi_get_undefined(env, &dummy);
NAPI_FATAL_IF_FAILED(status, "ThrowDefaultError", "napi_get_undefined");

if (info[0].As<Boolean>().Value()) {
// Provoke N-API into setting an error, then use the `Napi::Error::New`
// factory with only the `env` parameter to throw an exception generated
// from the last error.
uint32_t dummy_uint32;
status = napi_get_value_uint32(env, dummy, &dummy_uint32);
if (status == napi_ok) {
Error::Fatal("ThrowDefaultError", "napi_get_value_uint32");
}
// We cannot use `NAPI_THROW_IF_FAILED()` here because we do not wish
// control to pass back to the engine if we throw an exception here and C++
// exceptions are turned on.
Napi::Error::New(env).ThrowAsJavaScriptException();
}

// Produce and throw a second error that has different content than the one
// above. If the one above was thrown, then throwing the one below should
// have the effect of re-throwing the one above.
status = napi_get_named_property(env, dummy, "xyzzy", &dummy);
if (status == napi_ok) {
Error::Fatal("ThrowDefaultError", "napi_get_named_property");
}

// The macro creates a `Napi::Error` using the factory that takes only the
// env, however, it heeds the exception mechanism to be used.
NAPI_THROW_IF_FAILED_VOID(env, status);
}

} // end anonymous namespace

Object InitError(Env env) {
Expand All @@ -174,5 +208,6 @@ Object InitError(Env env) {
exports["catchAndRethrowErrorThatEscapesScope"] =
Function::New(env, CatchAndRethrowErrorThatEscapesScope);
exports["throwFatalError"] = Function::New(env, ThrowFatalError);
exports["throwDefaultError"] = Function::New(env, ThrowDefaultError);
return exports;
}
6 changes: 6 additions & 0 deletions test/error.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,10 @@ function test(bindingPath) {
assert.ifError(p.error);
assert.ok(p.stderr.toString().includes(
'FATAL ERROR: Error::ThrowFatalError This is a fatal error'));

assert.throws(() => binding.error.throwDefaultError(false),
/Cannot convert undefined or null to object/);

assert.throws(() => binding.error.throwDefaultError(true),
/A number was expected/);
}
2 changes: 1 addition & 1 deletion test/object/delete_property.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ function test(binding) {
function testShouldThrowErrorIfKeyIsInvalid(nativeDeleteProperty) {
assert.throws(() => {
nativeDeleteProperty(undefined, 'test');
}, /object was expected/);
}, /Cannot convert undefined or null to object/);
}

testDeleteProperty(binding.object.deletePropertyWithNapiValue);
Expand Down
2 changes: 1 addition & 1 deletion test/object/get_property.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ function test(binding) {
function testShouldThrowErrorIfKeyIsInvalid(nativeGetProperty) {
assert.throws(() => {
nativeGetProperty(undefined, 'test');
}, /object was expected/);
}, /Cannot convert undefined or null to object/);
}

testGetProperty(binding.object.getPropertyWithNapiValue);
Expand Down
2 changes: 1 addition & 1 deletion test/object/has_own_property.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ function test(binding) {
function testShouldThrowErrorIfKeyIsInvalid(nativeHasOwnProperty) {
assert.throws(() => {
nativeHasOwnProperty(undefined, 'test');
}, /object was expected/);
}, /Cannot convert undefined or null to object/);
}

testHasOwnProperty(binding.object.hasOwnPropertyWithNapiValue);
Expand Down
2 changes: 1 addition & 1 deletion test/object/has_property.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ function test(binding) {
function testShouldThrowErrorIfKeyIsInvalid(nativeHasProperty) {
assert.throws(() => {
nativeHasProperty(undefined, 'test');
}, /object was expected/);
}, /Cannot convert undefined or null to object/);
}

testHasProperty(binding.object.hasPropertyWithNapiValue);
Expand Down
2 changes: 1 addition & 1 deletion test/object/set_property.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ function test(binding) {
function testShouldThrowErrorIfKeyIsInvalid(nativeSetProperty) {
assert.throws(() => {
nativeSetProperty(undefined, 'test', 1);
}, /object was expected/);
}, /Cannot convert undefined or null to object/);
}

testSetProperty(binding.object.setPropertyWithNapiValue);
Expand Down