-
-
Notifications
You must be signed in to change notification settings - Fork 477
Add logic to handle graceful error handling when primitive errors are thrown by JS functions #1075
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
Changes from all commits
ed4d1c5
c89f0bf
02bcfbc
173c5bc
a0b3fe9
6918138
e0567d0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2593,14 +2593,80 @@ inline Error::Error() : ObjectReference() { | |
|
||
inline Error::Error(napi_env env, napi_value value) : ObjectReference(env, nullptr) { | ||
if (value != nullptr) { | ||
// Attempting to create a reference on the error object. | ||
// If it's not a Object/Function/Symbol, this call will return an error | ||
// status. | ||
napi_status status = napi_create_reference(env, value, 1, &_ref); | ||
|
||
if (status != napi_ok) { | ||
napi_value wrappedErrorObj; | ||
|
||
// Create an error object | ||
status = napi_create_object(env, &wrappedErrorObj); | ||
NAPI_FATAL_IF_FAILED(status, "Error::Error", "napi_create_object"); | ||
|
||
// property flag that we attach to show the error object is wrapped | ||
napi_property_descriptor wrapObjFlag = { | ||
ERROR_WRAP_VALUE, // Unique GUID identifier since Symbol isn't a | ||
// viable option | ||
nullptr, | ||
nullptr, | ||
nullptr, | ||
nullptr, | ||
Value::From(env, value), | ||
napi_enumerable, | ||
nullptr}; | ||
|
||
status = napi_define_properties(env, wrappedErrorObj, 1, &wrapObjFlag); | ||
NAPI_FATAL_IF_FAILED(status, "Error::Error", "napi_define_properties"); | ||
|
||
// Create a reference on the newly wrapped object | ||
status = napi_create_reference(env, wrappedErrorObj, 1, &_ref); | ||
} | ||
|
||
// Avoid infinite recursion in the failure case. | ||
// Don't try to construct & throw another Error instance. | ||
NAPI_FATAL_IF_FAILED(status, "Error::Error", "napi_create_reference"); | ||
} | ||
} | ||
|
||
inline Object Error::Value() const { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it might make sense for this to return Value instead of Object (Values can be objects), the main concern is that would potentially be breaking. The reason is that we wrapping so that we can have a value versus just objects. If its possible we might want two methods one which returns a Value and one which returns an Object in order to avoid breaking existing code. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I tried setting the return type to Value and it seems to pass all the test suites. Although I am not too sure if that breaks any conventions. (i.e, In the base Reference class, For the second part then, can I declare two public methods There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If is possible to have 2 methods both called Value() which return different types. If so that might make sense assuming the compiler will select the most appropriate one based on how you use the return value? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, I am not too sure if it's possible to overload base on return value in C++ (without using templates) according to this post (https://stackoverflow.com/questions/9568852/overloading-by-return-type). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. After discussing in Node-API team meeting I think this is ok as is. |
||
if (_ref == nullptr) { | ||
return Object(_env, nullptr); | ||
} | ||
gabrielschulhof marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
napi_value refValue; | ||
napi_status status = napi_get_reference_value(_env, _ref, &refValue); | ||
NAPI_THROW_IF_FAILED(_env, status, Object()); | ||
|
||
napi_valuetype type; | ||
status = napi_typeof(_env, refValue, &type); | ||
NAPI_THROW_IF_FAILED(_env, status, Object()); | ||
|
||
// If refValue isn't a symbol, then we proceed to whether the refValue has the | ||
// wrapped error flag | ||
if (type != napi_symbol) { | ||
// We are checking if the object is wrapped | ||
bool isWrappedObject = false; | ||
|
||
status = napi_has_property( | ||
_env, refValue, String::From(_env, ERROR_WRAP_VALUE), &isWrappedObject); | ||
|
||
// Don't care about status | ||
if (isWrappedObject) { | ||
napi_value unwrappedValue; | ||
status = napi_get_property(_env, | ||
refValue, | ||
String::From(_env, ERROR_WRAP_VALUE), | ||
&unwrappedValue); | ||
NAPI_THROW_IF_FAILED(_env, status, Object()); | ||
|
||
return Object(_env, unwrappedValue); | ||
} | ||
} | ||
|
||
return Object(_env, refValue); | ||
} | ||
|
||
inline Error::Error(Error&& other) : ObjectReference(std::move(other)) { | ||
} | ||
|
||
|
@@ -2651,6 +2717,7 @@ inline const std::string& Error::Message() const NAPI_NOEXCEPT { | |
return _message; | ||
} | ||
|
||
// we created an object on the &_ref | ||
inline void Error::ThrowAsJavaScriptException() const { | ||
HandleScope scope(_env); | ||
if (!IsEmpty()) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -31,6 +31,7 @@ Object InitDate(Env env); | |
Object InitDataView(Env env); | ||
Object InitDataViewReadWrite(Env env); | ||
Object InitEnvCleanup(Env env); | ||
Object InitErrorHandlingPrim(Env env); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are these names consistent with the file names being used? This will help @deepakrkris with the unit test filtering. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @deepakrkris can you double-check this please? |
||
Object InitError(Env env); | ||
Object InitExternal(Env env); | ||
Object InitFunction(Env env); | ||
|
@@ -113,6 +114,7 @@ Object Init(Env env, Object exports) { | |
exports.Set("env_cleanup", InitEnvCleanup(env)); | ||
#endif | ||
exports.Set("error", InitError(env)); | ||
exports.Set("errorHandlingPrim", InitErrorHandlingPrim(env)); | ||
exports.Set("external", InitExternal(env)); | ||
exports.Set("function", InitFunction(env)); | ||
exports.Set("functionreference", InitFunctionReference(env)); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
#include <napi.h> | ||
|
||
namespace { | ||
void Test(const Napi::CallbackInfo& info) { | ||
info[0].As<Napi::Function>().Call({}); | ||
} | ||
|
||
} // namespace | ||
Napi::Object InitErrorHandlingPrim(Napi::Env env) { | ||
Napi::Object exports = Napi::Object::New(env); | ||
exports.Set("errorHandlingPrim", Napi::Function::New<Test>(env)); | ||
return exports; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
'use strict'; | ||
|
||
const assert = require('assert'); | ||
|
||
module.exports = require('./common').runTest((binding) => { | ||
test(binding.errorHandlingPrim); | ||
}); | ||
|
||
function canThrow (binding, errorMessage, errorType) { | ||
try { | ||
binding.errorHandlingPrim(() => { | ||
throw errorMessage; | ||
}); | ||
} catch (e) { | ||
// eslint-disable-next-line valid-typeof | ||
assert(typeof e === errorType); | ||
assert(e === errorMessage); | ||
} | ||
} | ||
|
||
function test (binding) { | ||
canThrow(binding, '404 server not found!', 'string'); | ||
canThrow(binding, 42, 'number'); | ||
canThrow(binding, Symbol.for('newSym'), 'symbol'); | ||
canThrow(binding, false, 'boolean'); | ||
canThrow(binding, BigInt(123), 'bigint'); | ||
canThrow(binding, () => { console.log('Logger shutdown incorrectly'); }, 'function'); | ||
canThrow(binding, { status: 403, errorMsg: 'Not authenticated' }, 'object'); | ||
} |
Uh oh!
There was an error while loading. Please reload this page.