diff --git a/doc/error_handling.md b/doc/error_handling.md index 50c04a0fe..57de85c9e 100644 --- a/doc/error_handling.md +++ b/doc/error_handling.md @@ -17,6 +17,7 @@ error-handling for C++ exceptions and JavaScript exceptions. The following sections explain the approach for each case: - [Handling Errors With C++ Exceptions](#exceptions) +- [Handling Errors With Maybe Type and C++ Exceptions Disabled](#noexceptions-maybe) - [Handling Errors Without C++ Exceptions](#noexceptions) @@ -70,7 +71,7 @@ when returning to JavaScript. ### Propagating a Node-API C++ exception ```cpp -Napi::Function jsFunctionThatThrows = someObj.As(); +Napi::Function jsFunctionThatThrows = someValue.As(); Napi::Value result = jsFunctionThatThrows({ arg1, arg2 }); // other C++ statements // ... @@ -84,7 +85,7 @@ a JavaScript exception when returning to JavaScript. ### Handling a Node-API C++ exception ```cpp -Napi::Function jsFunctionThatThrows = someObj.As(); +Napi::Function jsFunctionThatThrows = someValue.As(); Napi::Value result; try { result = jsFunctionThatThrows({ arg1, arg2 }); @@ -96,6 +97,70 @@ try { Since the exception was caught here, it will not be propagated as a JavaScript exception. + + +## Handling Errors With Maybe Type and C++ Exceptions Disabled + +If C++ exceptions are disabled (for more info see: [Setup](setup.md)), then the +`Napi::Error` class does not extend `std::exception`. This means that any calls to +node-addon-api functions do not throw a C++ exceptions. Instead, these node-api +functions that call into JavaScript are returning with `Maybe` boxed values. +In that case, the calling side should convert the `Maybe` boxed values with +checks to ensure that the call did succeed and therefore no exception is pending. +If the check fails, that is to say, the returning value is _empty_, the calling +side should determine what to do with `env.GetAndClearPendingException()` before +attempting to call another node-api (for more info see: [Env](env.md)). + +The conversion from the `Maybe` boxed value to the actual return value is +enforced by compilers so that the exceptions must be properly handled before +continuing. + +## Examples with Maybe Type and C++ exceptions disabled + +### Throwing a JS exception + +```cpp +Napi::Env env = ... +Napi::Error::New(env, "Example exception").ThrowAsJavaScriptException(); +return; +``` + +After throwing a JavaScript exception, the code should generally return +immediately from the native callback, after performing any necessary cleanup. + +### Propagating a Node-API JS exception + +```cpp +Napi::Env env = ... +Napi::Function jsFunctionThatThrows = someValue.As(); +Maybe maybeResult = jsFunctionThatThrows({ arg1, arg2 }); +Napi::Value result; +if (!maybeResult.To(&result)) { + // The Maybe is empty, calling into js failed, cleaning up... + // It is recommended to return an empty Maybe if the procedure failed. + return result; +} +``` + +If `maybeResult.To(&result)` returns false a JavaScript exception is pending. +To let the exception propagate, the code should generally return immediately +from the native callback, after performing any necessary cleanup. + +### Handling a Node-API JS exception + +```cpp +Napi::Env env = ... +Napi::Function jsFunctionThatThrows = someValue.As(); +Maybe maybeResult = jsFunctionThatThrows({ arg1, arg2 }); +if (maybeResult.IsNothing()) { + Napi::Error e = env.GetAndClearPendingException(); + cerr << "Caught JavaScript exception: " + e.Message(); +} +``` + +Since the exception was cleared here, it will not be propagated as a JavaScript +exception after the native callback returns. + ## Handling Errors Without C++ Exceptions @@ -127,7 +192,7 @@ immediately from the native callback, after performing any necessary cleanup. ```cpp Napi::Env env = ... -Napi::Function jsFunctionThatThrows = someObj.As(); +Napi::Function jsFunctionThatThrows = someValue.As(); Napi::Value result = jsFunctionThatThrows({ arg1, arg2 }); if (env.IsExceptionPending()) { Error e = env.GetAndClearPendingException(); @@ -143,7 +208,7 @@ the native callback, after performing any necessary cleanup. ```cpp Napi::Env env = ... -Napi::Function jsFunctionThatThrows = someObj.As(); +Napi::Function jsFunctionThatThrows = someValue.As(); Napi::Value result = jsFunctionThatThrows({ arg1, arg2 }); if (env.IsExceptionPending()) { Napi::Error e = env.GetAndClearPendingException(); diff --git a/doc/maybe.md b/doc/maybe.md new file mode 100644 index 000000000..dc71c0750 --- /dev/null +++ b/doc/maybe.md @@ -0,0 +1,76 @@ +# Maybe (template) + +Class `Napi::Maybe` represents a value that may be empty: every `Maybe` is +either `Just` and contains a value, or `Nothing`, and does not. `Maybe` types +are very common in node-addon-api code, as they represent that the function may +throw a JavaScript exception and cause the program to be unable to evaluate any +JavaScript code until the exception has been handled. + +Typically, the value wrapped in `Napi::Maybe` is [`Napi::Value`] and its +subclasses. + +## Methods + +### IsNothing + +```cpp +template +bool Napi::Maybe::IsNothing() const; +``` + +Returns `true` if the `Maybe` is `Nothing` and does not contain a value, and +`false` otherwise. + +### IsJust + +```cpp +template +bool Napi::Maybe::IsJust() const; +``` + +Returns `true` if the `Maybe` is `Just` and contains a value, and `false` +otherwise. + +### Check + +```cpp +template +void Napi::Maybe::Check() const; +``` + +Short-hand for `Maybe::Unwrap()`, which doesn't return a value. Could be used +where the actual value of the Maybe is not needed like `Object::Set`. +If this Maybe is nothing (empty), node-addon-api will crash the +process. + +### Unwrap + +```cpp +template +T Napi::Maybe::Unwrap() const; +``` + +Return the value of type `T` contained in the Maybe. If this Maybe is +nothing (empty), node-addon-api will crash the process. + +### UnwrapOr + +```cpp +template +T Napi::Maybe::UnwrapOr(const T& default_value) const; +``` + +Return the value of type T contained in the Maybe, or use a default +value if this Maybe is nothing (empty). + +### UnwrapTo + +```cpp +template +bool Napi::Maybe::UnwrapTo(T* result) const; +``` + +Converts this Maybe to a value of type `T` in the `out`. If this Maybe is +nothing (empty), `false` is returned and `out` is left untouched. + +[`Napi::Value`]: ./value.md diff --git a/doc/setup.md b/doc/setup.md index aec397e61..5db3452ad 100644 --- a/doc/setup.md +++ b/doc/setup.md @@ -54,6 +54,15 @@ To use **Node-API** in a native module: ```gyp 'defines': [ 'NAPI_DISABLE_CPP_EXCEPTIONS' ], ``` + + If you decide to use node-addon-api without C++ exceptions enabled, please + consider enabling node-addon-api safe API type guards to ensure the proper + exception handling pattern: + +```gyp + 'defines': [ 'NODE_ADDON_API_ENABLE_MAYBE' ], +``` + 4. If you would like your native addon to support OSX, please also add the following settings in the `binding.gyp` file: diff --git a/napi-inl.h b/napi-inl.h index 663f229be..25c92a7d9 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -377,6 +377,72 @@ inline napi_value RegisterModule(napi_env env, }); } +//////////////////////////////////////////////////////////////////////////////// +// Maybe class +//////////////////////////////////////////////////////////////////////////////// + +template +bool Maybe::IsNothing() const { + return !_has_value; +} + +template +bool Maybe::IsJust() const { + return _has_value; +} + +template +void Maybe::Check() const { + NAPI_CHECK(IsJust(), "Napi::Maybe::Check", "Maybe value is Nothing."); +} + +template +T Maybe::Unwrap() const { + NAPI_CHECK(IsJust(), "Napi::Maybe::Unwrap", "Maybe value is Nothing."); + return _value; +} + +template +T Maybe::UnwrapOr(const T& default_value) const { + return _has_value ? _value : default_value; +} + +template +bool Maybe::UnwrapTo(T* out) const { + if (IsJust()) { + *out = _value; + return true; + }; + return false; +} + +template +bool Maybe::operator==(const Maybe& other) const { + return (IsJust() == other.IsJust()) && + (!IsJust() || Unwrap() == other.Unwrap()); +} + +template +bool Maybe::operator!=(const Maybe& other) const { + return !operator==(other); +} + +template +Maybe::Maybe() : _has_value(false) {} + +template +Maybe::Maybe(const T& t) : _has_value(true), _value(t) {} + +template +inline Maybe Nothing() { + return Maybe(); +} + +template +inline Maybe Just(const T& t) { + return Maybe(t); +} + //////////////////////////////////////////////////////////////////////////////// // Env class //////////////////////////////////////////////////////////////////////////////// @@ -426,20 +492,20 @@ inline Error Env::GetAndClearPendingException() { return Error(_env, value); } -inline Value Env::RunScript(const char* utf8script) { +inline MaybeOrValue Env::RunScript(const char* utf8script) { String script = String::New(_env, utf8script); return RunScript(script); } -inline Value Env::RunScript(const std::string& utf8script) { +inline MaybeOrValue Env::RunScript(const std::string& utf8script) { return RunScript(utf8script.c_str()); } -inline Value Env::RunScript(String script) { +inline MaybeOrValue Env::RunScript(String script) { napi_value result; napi_status status = napi_run_script(_env, script, &result); - NAPI_THROW_IF_FAILED(_env, status, Undefined()); - return Value(_env, result); + NAPI_RETURN_OR_THROW_IF_FAILED( + _env, status, Napi::Value(_env, result), Napi::Value); } #if NAPI_VERSION > 5 @@ -658,32 +724,32 @@ inline T Value::As() const { return T(_env, _value); } -inline Boolean Value::ToBoolean() const { +inline MaybeOrValue Value::ToBoolean() const { napi_value result; napi_status status = napi_coerce_to_bool(_env, _value, &result); - NAPI_THROW_IF_FAILED(_env, status, Boolean()); - return Boolean(_env, result); + NAPI_RETURN_OR_THROW_IF_FAILED( + _env, status, Napi::Boolean(_env, result), Napi::Boolean); } -inline Number Value::ToNumber() const { +inline MaybeOrValue Value::ToNumber() const { napi_value result; napi_status status = napi_coerce_to_number(_env, _value, &result); - NAPI_THROW_IF_FAILED(_env, status, Number()); - return Number(_env, result); + NAPI_RETURN_OR_THROW_IF_FAILED( + _env, status, Napi::Number(_env, result), Napi::Number); } -inline String Value::ToString() const { +inline MaybeOrValue Value::ToString() const { napi_value result; napi_status status = napi_coerce_to_string(_env, _value, &result); - NAPI_THROW_IF_FAILED(_env, status, String()); - return String(_env, result); + NAPI_RETURN_OR_THROW_IF_FAILED( + _env, status, Napi::String(_env, result), Napi::String); } -inline Object Value::ToObject() const { +inline MaybeOrValue Value::ToObject() const { napi_value result; napi_status status = napi_coerce_to_object(_env, _value, &result); - NAPI_THROW_IF_FAILED(_env, status, Object()); - return Object(_env, result); + NAPI_RETURN_OR_THROW_IF_FAILED( + _env, status, Napi::Object(_env, result), Napi::Object); } //////////////////////////////////////////////////////////////////////////////// @@ -999,29 +1065,56 @@ inline Symbol Symbol::New(napi_env env, napi_value description) { return Symbol(env, value); } -inline Symbol Symbol::WellKnown(napi_env env, const std::string& name) { +inline MaybeOrValue Symbol::WellKnown(napi_env env, + const std::string& name) { +#if defined(NODE_ADDON_API_ENABLE_MAYBE) + Value symbol_obj; + Value symbol_value; + if (Napi::Env(env).Global().Get("Symbol").UnwrapTo(&symbol_obj) && + symbol_obj.As().Get(name).UnwrapTo(&symbol_value)) { + return Just(symbol_value.As()); + } + return Nothing(); +#else return Napi::Env(env).Global().Get("Symbol").As().Get(name).As(); +#endif } -inline Symbol Symbol::For(napi_env env, const std::string& description) { +inline MaybeOrValue Symbol::For(napi_env env, + const std::string& description) { napi_value descriptionValue = String::New(env, description); return Symbol::For(env, descriptionValue); } -inline Symbol Symbol::For(napi_env env, const char* description) { +inline MaybeOrValue Symbol::For(napi_env env, const char* description) { napi_value descriptionValue = String::New(env, description); return Symbol::For(env, descriptionValue); } -inline Symbol Symbol::For(napi_env env, String description) { +inline MaybeOrValue Symbol::For(napi_env env, String description) { return Symbol::For(env, static_cast(description)); } -inline Symbol Symbol::For(napi_env env, napi_value description) { - Object symbObject = Napi::Env(env).Global().Get("Symbol").As(); - auto forSymb = - symbObject.Get("for").As().Call(symbObject, {description}); - return forSymb.As(); +inline MaybeOrValue Symbol::For(napi_env env, napi_value description) { +#if defined(NODE_ADDON_API_ENABLE_MAYBE) + Value symbol_obj; + Value symbol_for_value; + Value symbol_value; + if (Napi::Env(env).Global().Get("Symbol").UnwrapTo(&symbol_obj) && + symbol_obj.As().Get("for").UnwrapTo(&symbol_for_value) && + symbol_for_value.As() + .Call(symbol_obj, {description}) + .UnwrapTo(&symbol_value)) { + return Just(symbol_value.As()); + } + return Nothing(); +#else + Object symbol_obj = Napi::Env(env).Global().Get("Symbol").As(); + return symbol_obj.Get("for") + .As() + .Call(symbol_obj, {description}) + .As(); +#endif } inline Symbol::Symbol() : Name() { @@ -1136,12 +1229,23 @@ String String::From(napi_env env, const T& value) { template inline Object::PropertyLValue::operator Value() const { - return Object(_env, _object).Get(_key); + MaybeOrValue val = Object(_env, _object).Get(_key); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + return val.Unwrap(); +#else + return val; +#endif } template template inline Object::PropertyLValue& Object::PropertyLValue::operator =(ValueType value) { - Object(_env, _object).Set(_key, value); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + MaybeOrValue result = +#endif + Object(_env, _object).Set(_key, value); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + result.Unwrap(); +#endif return *this; } @@ -1174,208 +1278,192 @@ inline Object::PropertyLValue Object::operator [](uint32_t index) { return PropertyLValue(*this, index); } -inline Value Object::operator [](const char* utf8name) const { +inline MaybeOrValue Object::operator[](const char* utf8name) const { return Get(utf8name); } -inline Value Object::operator [](const std::string& utf8name) const { +inline MaybeOrValue Object::operator[]( + const std::string& utf8name) const { return Get(utf8name); } -inline Value Object::operator [](uint32_t index) const { +inline MaybeOrValue Object::operator[](uint32_t index) const { return Get(index); } -inline bool Object::Has(napi_value key) const { +inline MaybeOrValue Object::Has(napi_value key) const { bool result; napi_status status = napi_has_property(_env, _value, key, &result); - NAPI_THROW_IF_FAILED(_env, status, false); - return result; + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); } -inline bool Object::Has(Value key) const { +inline MaybeOrValue Object::Has(Value key) const { bool result; napi_status status = napi_has_property(_env, _value, key, &result); - NAPI_THROW_IF_FAILED(_env, status, false); - return result; + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); } -inline bool Object::Has(const char* utf8name) const { +inline MaybeOrValue Object::Has(const char* utf8name) const { bool result; napi_status status = napi_has_named_property(_env, _value, utf8name, &result); - NAPI_THROW_IF_FAILED(_env, status, false); - return result; + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); } -inline bool Object::Has(const std::string& utf8name) const { +inline MaybeOrValue Object::Has(const std::string& utf8name) const { return Has(utf8name.c_str()); } -inline bool Object::HasOwnProperty(napi_value key) const { +inline MaybeOrValue Object::HasOwnProperty(napi_value key) const { bool result; napi_status status = napi_has_own_property(_env, _value, key, &result); - NAPI_THROW_IF_FAILED(_env, status, false); - return result; + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); } -inline bool Object::HasOwnProperty(Value key) const { +inline MaybeOrValue Object::HasOwnProperty(Value key) const { bool result; napi_status status = napi_has_own_property(_env, _value, key, &result); - NAPI_THROW_IF_FAILED(_env, status, false); - return result; + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); } -inline bool Object::HasOwnProperty(const char* utf8name) const { +inline MaybeOrValue Object::HasOwnProperty(const char* utf8name) const { napi_value key; napi_status status = napi_create_string_utf8(_env, utf8name, std::strlen(utf8name), &key); - NAPI_THROW_IF_FAILED(_env, status, false); + NAPI_MAYBE_THROW_IF_FAILED(_env, status, bool); return HasOwnProperty(key); } -inline bool Object::HasOwnProperty(const std::string& utf8name) const { +inline MaybeOrValue Object::HasOwnProperty( + const std::string& utf8name) const { return HasOwnProperty(utf8name.c_str()); } -inline Value Object::Get(napi_value key) const { +inline MaybeOrValue Object::Get(napi_value key) const { napi_value result; napi_status status = napi_get_property(_env, _value, key, &result); - NAPI_THROW_IF_FAILED(_env, status, Value()); - return Value(_env, result); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, Value(_env, result), Value); } -inline Value Object::Get(Value key) const { +inline MaybeOrValue Object::Get(Value key) const { napi_value result; napi_status status = napi_get_property(_env, _value, key, &result); - NAPI_THROW_IF_FAILED(_env, status, Value()); - return Value(_env, result); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, Value(_env, result), Value); } -inline Value Object::Get(const char* utf8name) const { +inline MaybeOrValue Object::Get(const char* utf8name) const { napi_value result; napi_status status = napi_get_named_property(_env, _value, utf8name, &result); - NAPI_THROW_IF_FAILED(_env, status, Value()); - return Value(_env, result); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, Value(_env, result), Value); } -inline Value Object::Get(const std::string& utf8name) const { +inline MaybeOrValue Object::Get(const std::string& utf8name) const { return Get(utf8name.c_str()); } template -inline bool Object::Set(napi_value key, const ValueType& value) { +inline MaybeOrValue Object::Set(napi_value key, const ValueType& value) { napi_status status = napi_set_property(_env, _value, key, Value::From(_env, value)); - NAPI_THROW_IF_FAILED(_env, status, false); - return true; + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); } template -inline bool Object::Set(Value key, const ValueType& value) { +inline MaybeOrValue Object::Set(Value key, const ValueType& value) { napi_status status = napi_set_property(_env, _value, key, Value::From(_env, value)); - NAPI_THROW_IF_FAILED(_env, status, false); - return true; + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); } template -inline bool Object::Set(const char* utf8name, const ValueType& value) { +inline MaybeOrValue Object::Set(const char* utf8name, + const ValueType& value) { napi_status status = napi_set_named_property(_env, _value, utf8name, Value::From(_env, value)); - NAPI_THROW_IF_FAILED(_env, status, false); - return true; + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); } template -inline bool Object::Set(const std::string& utf8name, const ValueType& value) { +inline MaybeOrValue Object::Set(const std::string& utf8name, + const ValueType& value) { return Set(utf8name.c_str(), value); } -inline bool Object::Delete(napi_value key) { +inline MaybeOrValue Object::Delete(napi_value key) { bool result; napi_status status = napi_delete_property(_env, _value, key, &result); - NAPI_THROW_IF_FAILED(_env, status, false); - return result; + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); } -inline bool Object::Delete(Value key) { +inline MaybeOrValue Object::Delete(Value key) { bool result; napi_status status = napi_delete_property(_env, _value, key, &result); - NAPI_THROW_IF_FAILED(_env, status, false); - return result; + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); } -inline bool Object::Delete(const char* utf8name) { +inline MaybeOrValue Object::Delete(const char* utf8name) { return Delete(String::New(_env, utf8name)); } -inline bool Object::Delete(const std::string& utf8name) { +inline MaybeOrValue Object::Delete(const std::string& utf8name) { return Delete(String::New(_env, utf8name)); } -inline bool Object::Has(uint32_t index) const { +inline MaybeOrValue Object::Has(uint32_t index) const { bool result; napi_status status = napi_has_element(_env, _value, index, &result); - NAPI_THROW_IF_FAILED(_env, status, false); - return result; + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); } -inline Value Object::Get(uint32_t index) const { +inline MaybeOrValue Object::Get(uint32_t index) const { napi_value value; napi_status status = napi_get_element(_env, _value, index, &value); - NAPI_THROW_IF_FAILED(_env, status, Value()); - return Value(_env, value); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, Value(_env, value), Value); } template -inline bool Object::Set(uint32_t index, const ValueType& value) { +inline MaybeOrValue Object::Set(uint32_t index, const ValueType& value) { napi_status status = napi_set_element(_env, _value, index, Value::From(_env, value)); - NAPI_THROW_IF_FAILED(_env, status, false); - return true; + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); } -inline bool Object::Delete(uint32_t index) { +inline MaybeOrValue Object::Delete(uint32_t index) { bool result; napi_status status = napi_delete_element(_env, _value, index, &result); - NAPI_THROW_IF_FAILED(_env, status, false); - return result; + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); } -inline Array Object::GetPropertyNames() const { +inline MaybeOrValue Object::GetPropertyNames() const { napi_value result; napi_status status = napi_get_property_names(_env, _value, &result); - NAPI_THROW_IF_FAILED(_env, status, Array()); - return Array(_env, result); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, Array(_env, result), Array); } -inline bool Object::DefineProperty(const PropertyDescriptor& property) { +inline MaybeOrValue Object::DefineProperty( + const PropertyDescriptor& property) { napi_status status = napi_define_properties(_env, _value, 1, reinterpret_cast(&property)); - NAPI_THROW_IF_FAILED(_env, status, false); - return true; + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); } -inline bool Object::DefineProperties( +inline MaybeOrValue Object::DefineProperties( const std::initializer_list& properties) { napi_status status = napi_define_properties(_env, _value, properties.size(), reinterpret_cast(properties.begin())); - NAPI_THROW_IF_FAILED(_env, status, false); - return true; + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); } -inline bool Object::DefineProperties( +inline MaybeOrValue Object::DefineProperties( const std::vector& properties) { napi_status status = napi_define_properties(_env, _value, properties.size(), reinterpret_cast(properties.data())); - NAPI_THROW_IF_FAILED(_env, status, false); - return true; + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); } -inline bool Object::InstanceOf(const Function& constructor) const { +inline MaybeOrValue Object::InstanceOf( + const Function& constructor) const { bool result; napi_status status = napi_instanceof(_env, _value, constructor, &result); - NAPI_THROW_IF_FAILED(_env, status, false); - return result; + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); } template @@ -1415,16 +1503,14 @@ inline void Object::AddFinalizer(Finalizer finalizeCallback, } #if NAPI_VERSION >= 8 -inline bool Object::Freeze() { +inline MaybeOrValue Object::Freeze() { napi_status status = napi_object_freeze(_env, _value); - NAPI_THROW_IF_FAILED(_env, status, false); - return true; + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); } -inline bool Object::Seal() { +inline MaybeOrValue Object::Seal() { napi_status status = napi_object_seal(_env, _value); - NAPI_THROW_IF_FAILED(_env, status, false); - return true; + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); } #endif // NAPI_VERSION >= 8 @@ -2071,53 +2157,61 @@ inline Function::Function() : Object() { inline Function::Function(napi_env env, napi_value value) : Object(env, value) { } -inline Value Function::operator ()(const std::initializer_list& args) const { +inline MaybeOrValue Function::operator()( + const std::initializer_list& args) const { return Call(Env().Undefined(), args); } -inline Value Function::Call(const std::initializer_list& args) const { +inline MaybeOrValue Function::Call( + const std::initializer_list& args) const { return Call(Env().Undefined(), args); } -inline Value Function::Call(const std::vector& args) const { +inline MaybeOrValue Function::Call( + const std::vector& args) const { return Call(Env().Undefined(), args); } -inline Value Function::Call(size_t argc, const napi_value* args) const { +inline MaybeOrValue Function::Call(size_t argc, + const napi_value* args) const { return Call(Env().Undefined(), argc, args); } -inline Value Function::Call(napi_value recv, const std::initializer_list& args) const { +inline MaybeOrValue Function::Call( + napi_value recv, const std::initializer_list& args) const { return Call(recv, args.size(), args.begin()); } -inline Value Function::Call(napi_value recv, const std::vector& args) const { +inline MaybeOrValue Function::Call( + napi_value recv, const std::vector& args) const { return Call(recv, args.size(), args.data()); } -inline Value Function::Call(napi_value recv, size_t argc, const napi_value* args) const { +inline MaybeOrValue Function::Call(napi_value recv, + size_t argc, + const napi_value* args) const { napi_value result; napi_status status = napi_call_function( _env, recv, _value, argc, args, &result); - NAPI_THROW_IF_FAILED(_env, status, Value()); - return Value(_env, result); + NAPI_RETURN_OR_THROW_IF_FAILED( + _env, status, Napi::Value(_env, result), Napi::Value); } -inline Value Function::MakeCallback( +inline MaybeOrValue Function::MakeCallback( napi_value recv, const std::initializer_list& args, napi_async_context context) const { return MakeCallback(recv, args.size(), args.begin(), context); } -inline Value Function::MakeCallback( +inline MaybeOrValue Function::MakeCallback( napi_value recv, const std::vector& args, napi_async_context context) const { return MakeCallback(recv, args.size(), args.data(), context); } -inline Value Function::MakeCallback( +inline MaybeOrValue Function::MakeCallback( napi_value recv, size_t argc, const napi_value* args, @@ -2125,24 +2219,27 @@ inline Value Function::MakeCallback( napi_value result; napi_status status = napi_make_callback( _env, context, recv, _value, argc, args, &result); - NAPI_THROW_IF_FAILED(_env, status, Value()); - return Value(_env, result); + NAPI_RETURN_OR_THROW_IF_FAILED( + _env, status, Napi::Value(_env, result), Napi::Value); } -inline Object Function::New(const std::initializer_list& args) const { +inline MaybeOrValue Function::New( + const std::initializer_list& args) const { return New(args.size(), args.begin()); } -inline Object Function::New(const std::vector& args) const { +inline MaybeOrValue Function::New( + const std::vector& args) const { return New(args.size(), args.data()); } -inline Object Function::New(size_t argc, const napi_value* args) const { +inline MaybeOrValue Function::New(size_t argc, + const napi_value* args) const { napi_value result; napi_status status = napi_new_instance( _env, _value, argc, args, &result); - NAPI_THROW_IF_FAILED(_env, status, Object()); - return Object(_env, result); + NAPI_RETURN_OR_THROW_IF_FAILED( + _env, status, Napi::Object(_env, result), Napi::Object); } //////////////////////////////////////////////////////////////////////////////// @@ -2414,7 +2511,14 @@ inline const std::string& Error::Message() const NAPI_NOEXCEPT { // the std::string::operator=, because this method may not throw. } #else // NAPI_CPP_EXCEPTIONS +#if defined(NODE_ADDON_API_ENABLE_MAYBE) + Napi::Value message_val; + if (Get("message").UnwrapTo(&message_val)) { + _message = message_val.As(); + } +#else _message = Get("message").As(); +#endif #endif // NAPI_CPP_EXCEPTIONS } return _message; @@ -2731,101 +2835,147 @@ inline ObjectReference::ObjectReference(const ObjectReference& other) : Reference(other) { } -inline Napi::Value ObjectReference::Get(const char* utf8name) const { +inline MaybeOrValue ObjectReference::Get( + const char* utf8name) const { EscapableHandleScope scope(_env); - return scope.Escape(Value().Get(utf8name)); + MaybeOrValue result = Value().Get(utf8name); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap())); + } + return result; +#else + if (scope.Env().IsExceptionPending()) { + return Value(); + } + return scope.Escape(result); +#endif } -inline Napi::Value ObjectReference::Get(const std::string& utf8name) const { +inline MaybeOrValue ObjectReference::Get( + const std::string& utf8name) const { EscapableHandleScope scope(_env); - return scope.Escape(Value().Get(utf8name)); + MaybeOrValue result = Value().Get(utf8name); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap())); + } + return result; +#else + if (scope.Env().IsExceptionPending()) { + return Value(); + } + return scope.Escape(result); +#endif } -inline bool ObjectReference::Set(const char* utf8name, napi_value value) { +inline MaybeOrValue ObjectReference::Set(const char* utf8name, + napi_value value) { HandleScope scope(_env); return Value().Set(utf8name, value); } -inline bool ObjectReference::Set(const char* utf8name, Napi::Value value) { +inline MaybeOrValue ObjectReference::Set(const char* utf8name, + Napi::Value value) { HandleScope scope(_env); return Value().Set(utf8name, value); } -inline bool ObjectReference::Set(const char* utf8name, const char* utf8value) { +inline MaybeOrValue ObjectReference::Set(const char* utf8name, + const char* utf8value) { HandleScope scope(_env); return Value().Set(utf8name, utf8value); } -inline bool ObjectReference::Set(const char* utf8name, bool boolValue) { +inline MaybeOrValue ObjectReference::Set(const char* utf8name, + bool boolValue) { HandleScope scope(_env); return Value().Set(utf8name, boolValue); } -inline bool ObjectReference::Set(const char* utf8name, double numberValue) { +inline MaybeOrValue ObjectReference::Set(const char* utf8name, + double numberValue) { HandleScope scope(_env); return Value().Set(utf8name, numberValue); } -inline bool ObjectReference::Set(const std::string& utf8name, - napi_value value) { +inline MaybeOrValue ObjectReference::Set(const std::string& utf8name, + napi_value value) { HandleScope scope(_env); return Value().Set(utf8name, value); } -inline bool ObjectReference::Set(const std::string& utf8name, - Napi::Value value) { +inline MaybeOrValue ObjectReference::Set(const std::string& utf8name, + Napi::Value value) { HandleScope scope(_env); return Value().Set(utf8name, value); } -inline bool ObjectReference::Set(const std::string& utf8name, - std::string& utf8value) { +inline MaybeOrValue ObjectReference::Set(const std::string& utf8name, + std::string& utf8value) { HandleScope scope(_env); return Value().Set(utf8name, utf8value); } -inline bool ObjectReference::Set(const std::string& utf8name, bool boolValue) { +inline MaybeOrValue ObjectReference::Set(const std::string& utf8name, + bool boolValue) { HandleScope scope(_env); return Value().Set(utf8name, boolValue); } -inline bool ObjectReference::Set(const std::string& utf8name, - double numberValue) { +inline MaybeOrValue ObjectReference::Set(const std::string& utf8name, + double numberValue) { HandleScope scope(_env); return Value().Set(utf8name, numberValue); } -inline Napi::Value ObjectReference::Get(uint32_t index) const { +inline MaybeOrValue ObjectReference::Get(uint32_t index) const { EscapableHandleScope scope(_env); - return scope.Escape(Value().Get(index)); + MaybeOrValue result = Value().Get(index); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap())); + } + return result; +#else + if (scope.Env().IsExceptionPending()) { + return Value(); + } + return scope.Escape(result); +#endif } -inline bool ObjectReference::Set(uint32_t index, napi_value value) { +inline MaybeOrValue ObjectReference::Set(uint32_t index, + napi_value value) { HandleScope scope(_env); return Value().Set(index, value); } -inline bool ObjectReference::Set(uint32_t index, Napi::Value value) { +inline MaybeOrValue ObjectReference::Set(uint32_t index, + Napi::Value value) { HandleScope scope(_env); return Value().Set(index, value); } -inline bool ObjectReference::Set(uint32_t index, const char* utf8value) { +inline MaybeOrValue ObjectReference::Set(uint32_t index, + const char* utf8value) { HandleScope scope(_env); return Value().Set(index, utf8value); } -inline bool ObjectReference::Set(uint32_t index, const std::string& utf8value) { +inline MaybeOrValue ObjectReference::Set(uint32_t index, + const std::string& utf8value) { HandleScope scope(_env); return Value().Set(index, utf8value); } -inline bool ObjectReference::Set(uint32_t index, bool boolValue) { +inline MaybeOrValue ObjectReference::Set(uint32_t index, bool boolValue) { HandleScope scope(_env); return Value().Set(index, boolValue); } -inline bool ObjectReference::Set(uint32_t index, double numberValue) { +inline MaybeOrValue ObjectReference::Set(uint32_t index, + double numberValue) { HandleScope scope(_env); return Value().Set(index, numberValue); } @@ -2859,105 +3009,200 @@ inline FunctionReference& FunctionReference::operator =(FunctionReference&& othe return *this; } -inline Napi::Value FunctionReference::operator ()( +inline MaybeOrValue FunctionReference::operator()( const std::initializer_list& args) const { EscapableHandleScope scope(_env); - return scope.Escape(Value()(args)); + MaybeOrValue result = Value()(args); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap())); + } + return result; +#else + if (scope.Env().IsExceptionPending()) { + return Value(); + } + return scope.Escape(result); +#endif } -inline Napi::Value FunctionReference::Call(const std::initializer_list& args) const { +inline MaybeOrValue FunctionReference::Call( + const std::initializer_list& args) const { EscapableHandleScope scope(_env); - Napi::Value result = Value().Call(args); + MaybeOrValue result = Value().Call(args); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap())); + } + return result; +#else if (scope.Env().IsExceptionPending()) { return Value(); } return scope.Escape(result); +#endif } -inline Napi::Value FunctionReference::Call(const std::vector& args) const { +inline MaybeOrValue FunctionReference::Call( + const std::vector& args) const { EscapableHandleScope scope(_env); - Napi::Value result = Value().Call(args); + MaybeOrValue result = Value().Call(args); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap())); + } + return result; +#else if (scope.Env().IsExceptionPending()) { return Value(); } return scope.Escape(result); +#endif } -inline Napi::Value FunctionReference::Call( +inline MaybeOrValue FunctionReference::Call( napi_value recv, const std::initializer_list& args) const { EscapableHandleScope scope(_env); - Napi::Value result = Value().Call(recv, args); + MaybeOrValue result = Value().Call(recv, args); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap())); + } + return result; +#else if (scope.Env().IsExceptionPending()) { return Value(); } return scope.Escape(result); +#endif } -inline Napi::Value FunctionReference::Call( +inline MaybeOrValue FunctionReference::Call( napi_value recv, const std::vector& args) const { EscapableHandleScope scope(_env); - Napi::Value result = Value().Call(recv, args); + MaybeOrValue result = Value().Call(recv, args); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap())); + } + return result; +#else if (scope.Env().IsExceptionPending()) { return Value(); } return scope.Escape(result); +#endif } -inline Napi::Value FunctionReference::Call( +inline MaybeOrValue FunctionReference::Call( napi_value recv, size_t argc, const napi_value* args) const { EscapableHandleScope scope(_env); - Napi::Value result = Value().Call(recv, argc, args); + MaybeOrValue result = Value().Call(recv, argc, args); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap())); + } + return result; +#else if (scope.Env().IsExceptionPending()) { return Value(); } return scope.Escape(result); +#endif } -inline Napi::Value FunctionReference::MakeCallback( +inline MaybeOrValue FunctionReference::MakeCallback( napi_value recv, const std::initializer_list& args, napi_async_context context) const { EscapableHandleScope scope(_env); - Napi::Value result = Value().MakeCallback(recv, args, context); + MaybeOrValue result = Value().MakeCallback(recv, args, context); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap())); + } + + return result; +#else if (scope.Env().IsExceptionPending()) { return Value(); } return scope.Escape(result); +#endif } -inline Napi::Value FunctionReference::MakeCallback( +inline MaybeOrValue FunctionReference::MakeCallback( napi_value recv, const std::vector& args, napi_async_context context) const { EscapableHandleScope scope(_env); - Napi::Value result = Value().MakeCallback(recv, args, context); + MaybeOrValue result = Value().MakeCallback(recv, args, context); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap())); + } + return result; +#else if (scope.Env().IsExceptionPending()) { return Value(); } return scope.Escape(result); +#endif } -inline Napi::Value FunctionReference::MakeCallback( +inline MaybeOrValue FunctionReference::MakeCallback( napi_value recv, size_t argc, const napi_value* args, napi_async_context context) const { EscapableHandleScope scope(_env); - Napi::Value result = Value().MakeCallback(recv, argc, args, context); + MaybeOrValue result = + Value().MakeCallback(recv, argc, args, context); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap())); + } + return result; +#else if (scope.Env().IsExceptionPending()) { return Value(); } return scope.Escape(result); +#endif } -inline Object FunctionReference::New(const std::initializer_list& args) const { +inline MaybeOrValue FunctionReference::New( + const std::initializer_list& args) const { EscapableHandleScope scope(_env); - return scope.Escape(Value().New(args)).As(); + MaybeOrValue result = Value().New(args); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap()).As()); + } + return result; +#else + if (scope.Env().IsExceptionPending()) { + return Object(); + } + return scope.Escape(result).As(); +#endif } -inline Object FunctionReference::New(const std::vector& args) const { +inline MaybeOrValue FunctionReference::New( + const std::vector& args) const { EscapableHandleScope scope(_env); - return scope.Escape(Value().New(args)).As(); + MaybeOrValue result = Value().New(args); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap()).As()); + } + return result; +#else + if (scope.Env().IsExceptionPending()) { + return Object(); + } + return scope.Escape(result).As(); +#endif } //////////////////////////////////////////////////////////////////////////////// diff --git a/napi.h b/napi.h index c3e02cb88..8f4d39a6e 100644 --- a/napi.h +++ b/napi.h @@ -34,6 +34,13 @@ static_assert(sizeof(char16_t) == sizeof(wchar_t), "Size mismatch between char16 #endif #endif +// If C++ NAPI_CPP_EXCEPTIONS are enabled, NODE_ADDON_API_ENABLE_MAYBE should +// not be set +#if defined(NAPI_CPP_EXCEPTIONS) && defined(NODE_ADDON_API_ENABLE_MAYBE) +#error NODE_ADDON_API_ENABLE_MAYBE should not be set when \ + NAPI_CPP_EXCEPTIONS is defined. +#endif + #ifdef _NOEXCEPT #define NAPI_NOEXCEPT _NOEXCEPT #else @@ -77,20 +84,36 @@ static_assert(sizeof(char16_t) == sizeof(wchar_t), "Size mismatch between char16 return; \ } while (0) -#define NAPI_THROW_IF_FAILED(env, status, ...) \ - if ((status) != napi_ok) { \ - Napi::Error::New(env).ThrowAsJavaScriptException(); \ - return __VA_ARGS__; \ +#define NAPI_THROW_IF_FAILED(env, status, ...) \ + if ((status) != napi_ok) { \ + Napi::Error::New(env).ThrowAsJavaScriptException(); \ + return __VA_ARGS__; \ } -#define NAPI_THROW_IF_FAILED_VOID(env, status) \ - if ((status) != napi_ok) { \ - Napi::Error::New(env).ThrowAsJavaScriptException(); \ - return; \ +#define NAPI_THROW_IF_FAILED_VOID(env, status) \ + if ((status) != napi_ok) { \ + Napi::Error::New(env).ThrowAsJavaScriptException(); \ + return; \ } #endif // NAPI_CPP_EXCEPTIONS +#ifdef NODE_ADDON_API_ENABLE_MAYBE +#define NAPI_MAYBE_THROW_IF_FAILED(env, status, type) \ + NAPI_THROW_IF_FAILED(env, status, Napi::Nothing()) + +#define NAPI_RETURN_OR_THROW_IF_FAILED(env, status, result, type) \ + NAPI_MAYBE_THROW_IF_FAILED(env, status, type); \ + return Napi::Just(result); +#else +#define NAPI_MAYBE_THROW_IF_FAILED(env, status, type) \ + NAPI_THROW_IF_FAILED(env, status, type()) + +#define NAPI_RETURN_OR_THROW_IF_FAILED(env, status, result, type) \ + NAPI_MAYBE_THROW_IF_FAILED(env, status, type); \ + return result; +#endif + # define NAPI_DISALLOW_ASSIGN(CLASS) void operator=(const CLASS&) = delete; # define NAPI_DISALLOW_COPY(CLASS) CLASS(const CLASS&) = delete; @@ -98,13 +121,16 @@ static_assert(sizeof(char16_t) == sizeof(wchar_t), "Size mismatch between char16 NAPI_DISALLOW_ASSIGN(CLASS) \ NAPI_DISALLOW_COPY(CLASS) -#define NAPI_FATAL_IF_FAILED(status, location, message) \ - do { \ - if ((status) != napi_ok) { \ - Napi::Error::Fatal((location), (message)); \ - } \ +#define NAPI_CHECK(condition, location, message) \ + do { \ + if (!(condition)) { \ + Napi::Error::Fatal((location), (message)); \ + } \ } while (0) +#define NAPI_FATAL_IF_FAILED(status, location, message) \ + NAPI_CHECK((status) == napi_ok, location, message) + //////////////////////////////////////////////////////////////////////////////// /// Node-API C++ Wrapper Classes /// @@ -165,6 +191,67 @@ namespace Napi { class MemoryManagement; + /// A simple Maybe type, representing an object which may or may not have a + /// value. + /// + /// If an API method returns a Maybe<>, the API method can potentially fail + /// either because an exception is thrown, or because an exception is pending, + /// e.g. because a previous API call threw an exception that hasn't been + /// caught yet. In that case, a "Nothing" value is returned. + template + class Maybe { + public: + bool IsNothing() const; + bool IsJust() const; + + /// Short-hand for Unwrap(), which doesn't return a value. Could be used + /// where the actual value of the Maybe is not needed like Object::Set. + /// If this Maybe is nothing (empty), node-addon-api will crash the + /// process. + void Check() const; + + /// Return the value of type T contained in the Maybe. If this Maybe is + /// nothing (empty), node-addon-api will crash the process. + T Unwrap() const; + + /// Return the value of type T contained in the Maybe, or using a default + /// value if this Maybe is nothing (empty). + T UnwrapOr(const T& default_value) const; + + /// Converts this Maybe to a value of type T in the out. If this Maybe is + /// nothing (empty), `false` is returned and `out` is left untouched. + bool UnwrapTo(T* out) const; + + bool operator==(const Maybe& other) const; + bool operator!=(const Maybe& other) const; + + private: + Maybe(); + explicit Maybe(const T& t); + + bool _has_value; + T _value; + + template + friend Maybe Nothing(); + template + friend Maybe Just(const U& u); + }; + + template + inline Maybe Nothing(); + + template + inline Maybe Just(const T& t); + +#if defined(NODE_ADDON_API_ENABLE_MAYBE) + template + using MaybeOrValue = Maybe; +#else + template + using MaybeOrValue = T; +#endif + /// Environment for Node-API values and operations. /// /// All Node-API values and operations must be associated with an environment. @@ -197,9 +284,9 @@ namespace Napi { bool IsExceptionPending() const; Error GetAndClearPendingException(); - Value RunScript(const char* utf8script); - Value RunScript(const std::string& utf8script); - Value RunScript(String script); + MaybeOrValue RunScript(const char* utf8script); + MaybeOrValue RunScript(const std::string& utf8script); + MaybeOrValue RunScript(String script); #if NAPI_VERSION > 5 template T* GetInstanceData(); @@ -311,12 +398,16 @@ namespace Napi { /// value type will throw `Napi::Error`. template T As() const; - Boolean ToBoolean() const; ///< Coerces a value to a JavaScript boolean. - Number ToNumber() const; ///< Coerces a value to a JavaScript number. - String ToString() const; ///< Coerces a value to a JavaScript string. - Object ToObject() const; ///< Coerces a value to a JavaScript object. + MaybeOrValue ToBoolean() + const; ///< Coerces a value to a JavaScript boolean. + MaybeOrValue ToNumber() + const; ///< Coerces a value to a JavaScript number. + MaybeOrValue ToString() + const; ///< Coerces a value to a JavaScript string. + MaybeOrValue ToObject() + const; ///< Coerces a value to a JavaScript object. - protected: + protected: /// !cond INTERNAL napi_env _env; napi_value _value; @@ -536,19 +627,20 @@ namespace Napi { ); /// Get a public Symbol (e.g. Symbol.iterator). - static Symbol WellKnown(napi_env, const std::string& name); + static MaybeOrValue WellKnown(napi_env, const std::string& name); // Create a symbol in the global registry, UTF-8 Encoded cpp string - static Symbol For(napi_env env, const std::string& description); + static MaybeOrValue For(napi_env env, + const std::string& description); // Create a symbol in the global registry, C style string (null terminated) - static Symbol For(napi_env env, const char* description); + static MaybeOrValue For(napi_env env, const char* description); // Create a symbol in the global registry, String value describing the symbol - static Symbol For(napi_env env, String description); + static MaybeOrValue For(napi_env env, String description); // Create a symbol in the global registry, napi_value describing the symbol - static Symbol For(napi_env env, napi_value description); + static MaybeOrValue For(napi_env env, napi_value description); Symbol(); ///< Creates a new _empty_ Symbol instance. Symbol(napi_env env, @@ -558,16 +650,22 @@ namespace Napi { /// A JavaScript object value. class Object : public Value { public: - /// Enables property and element assignments using indexing syntax. - /// - /// Example: - /// - /// Napi::Value propertyValue = object1['A']; - /// object2['A'] = propertyValue; - /// Napi::Value elementValue = array[0]; - /// array[1] = elementValue; - template - class PropertyLValue { + /// Enables property and element assignments using indexing syntax. + /// + /// This is a convenient helper to get and set object properties. As + /// getting and setting object properties may throw with JavaScript + /// exceptions, it is notable that these operations may fail. + /// When NODE_ADDON_API_ENABLE_MAYBE is defined, the process will abort + /// on JavaScript exceptions. + /// + /// Example: + /// + /// Napi::Value propertyValue = object1['A']; + /// object2['A'] = propertyValue; + /// Napi::Value elementValue = array[0]; + /// array[1] = elementValue; + template + class PropertyLValue { public: /// Converts an L-value to a value. operator Value() const; @@ -585,7 +683,7 @@ namespace Napi { Key _key; friend class Napi::Object; - }; + }; /// Creates a new Object value. static Object New(napi_env env ///< Node-API environment @@ -611,161 +709,169 @@ namespace Napi { ); /// Gets a named property. - Value operator []( - const char* utf8name ///< UTF-8 encoded null-terminated property name + MaybeOrValue operator[]( + const char* utf8name ///< UTF-8 encoded null-terminated property name ) const; /// Gets a named property. - Value operator []( - const std::string& utf8name ///< UTF-8 encoded property name + MaybeOrValue operator[]( + const std::string& utf8name ///< UTF-8 encoded property name ) const; /// Gets an indexed property or array element. - Value operator []( - uint32_t index ///< Property / element index + MaybeOrValue operator[](uint32_t index ///< Property / element index ) const; /// Checks whether a property is present. - bool Has( - napi_value key ///< Property key primitive + MaybeOrValue Has(napi_value key ///< Property key primitive ) const; /// Checks whether a property is present. - bool Has( - Value key ///< Property key + MaybeOrValue Has(Value key ///< Property key ) const; /// Checks whether a named property is present. - bool Has( - const char* utf8name ///< UTF-8 encoded null-terminated property name + MaybeOrValue Has( + const char* utf8name ///< UTF-8 encoded null-terminated property name ) const; /// Checks whether a named property is present. - bool Has( - const std::string& utf8name ///< UTF-8 encoded property name + MaybeOrValue Has( + const std::string& utf8name ///< UTF-8 encoded property name ) const; /// Checks whether a own property is present. - bool HasOwnProperty( - napi_value key ///< Property key primitive + MaybeOrValue HasOwnProperty( + napi_value key ///< Property key primitive ) const; /// Checks whether a own property is present. - bool HasOwnProperty( - Value key ///< Property key + MaybeOrValue HasOwnProperty(Value key ///< Property key ) const; /// Checks whether a own property is present. - bool HasOwnProperty( - const char* utf8name ///< UTF-8 encoded null-terminated property name + MaybeOrValue HasOwnProperty( + const char* utf8name ///< UTF-8 encoded null-terminated property name ) const; /// Checks whether a own property is present. - bool HasOwnProperty( - const std::string& utf8name ///< UTF-8 encoded property name + MaybeOrValue HasOwnProperty( + const std::string& utf8name ///< UTF-8 encoded property name ) const; /// Gets a property. - Value Get( - napi_value key ///< Property key primitive + MaybeOrValue Get(napi_value key ///< Property key primitive ) const; /// Gets a property. - Value Get( - Value key ///< Property key + MaybeOrValue Get(Value key ///< Property key ) const; /// Gets a named property. - Value Get( - const char* utf8name ///< UTF-8 encoded null-terminated property name + MaybeOrValue Get( + const char* utf8name ///< UTF-8 encoded null-terminated property name ) const; /// Gets a named property. - Value Get( - const std::string& utf8name ///< UTF-8 encoded property name + MaybeOrValue Get( + const std::string& utf8name ///< UTF-8 encoded property name ) const; /// Sets a property. template - bool Set(napi_value key, ///< Property key primitive - const ValueType& value ///< Property value primitive + MaybeOrValue Set(napi_value key, ///< Property key primitive + const ValueType& value ///< Property value primitive ); /// Sets a property. template - bool Set(Value key, ///< Property key - const ValueType& value ///< Property value + MaybeOrValue Set(Value key, ///< Property key + const ValueType& value ///< Property value ); /// Sets a named property. template - bool Set( + MaybeOrValue Set( const char* utf8name, ///< UTF-8 encoded null-terminated property name const ValueType& value); /// Sets a named property. template - bool Set(const std::string& utf8name, ///< UTF-8 encoded property name - const ValueType& value ///< Property value primitive + MaybeOrValue Set( + const std::string& utf8name, ///< UTF-8 encoded property name + const ValueType& value ///< Property value primitive ); /// Delete property. - bool Delete( - napi_value key ///< Property key primitive + MaybeOrValue Delete(napi_value key ///< Property key primitive ); /// Delete property. - bool Delete( - Value key ///< Property key + MaybeOrValue Delete(Value key ///< Property key ); /// Delete property. - bool Delete( - const char* utf8name ///< UTF-8 encoded null-terminated property name + MaybeOrValue Delete( + const char* utf8name ///< UTF-8 encoded null-terminated property name ); /// Delete property. - bool Delete( - const std::string& utf8name ///< UTF-8 encoded property name + MaybeOrValue Delete( + const std::string& utf8name ///< UTF-8 encoded property name ); /// Checks whether an indexed property is present. - bool Has( - uint32_t index ///< Property / element index + MaybeOrValue Has(uint32_t index ///< Property / element index ) const; /// Gets an indexed property or array element. - Value Get( - uint32_t index ///< Property / element index + MaybeOrValue Get(uint32_t index ///< Property / element index ) const; /// Sets an indexed property or array element. template - bool Set(uint32_t index, ///< Property / element index - const ValueType& value ///< Property value primitive + MaybeOrValue Set(uint32_t index, ///< Property / element index + const ValueType& value ///< Property value primitive ); /// Deletes an indexed property or array element. - bool Delete( - uint32_t index ///< Property / element index + MaybeOrValue Delete(uint32_t index ///< Property / element index ); - Array GetPropertyNames() const; ///< Get all property names + /// This operation can fail in case of Proxy.[[OwnPropertyKeys]] and + /// Proxy.[[GetOwnProperty]] calling into JavaScript. See: + /// - + /// https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-ownpropertykeys + /// - + /// https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-getownproperty-p + MaybeOrValue GetPropertyNames() const; ///< Get all property names /// Defines a property on the object. - bool DefineProperty( + /// + /// This operation can fail in case of Proxy.[[DefineOwnProperty]] calling + /// into JavaScript. See + /// https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-defineownproperty-p-desc + MaybeOrValue DefineProperty( const PropertyDescriptor& property ///< Descriptor for the property to be defined ); /// Defines properties on the object. - bool DefineProperties( + /// + /// This operation can fail in case of Proxy.[[DefineOwnProperty]] calling + /// into JavaScript. See + /// https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-defineownproperty-p-desc + MaybeOrValue DefineProperties( const std::initializer_list& properties ///< List of descriptors for the properties to be defined ); /// Defines properties on the object. - bool DefineProperties( + /// + /// This operation can fail in case of Proxy.[[DefineOwnProperty]] calling + /// into JavaScript. See + /// https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-defineownproperty-p-desc + MaybeOrValue DefineProperties( const std::vector& properties ///< Vector of descriptors for the properties to be defined ); @@ -773,8 +879,13 @@ namespace Napi { /// Checks if an object is an instance created by a constructor function. /// /// This is equivalent to the JavaScript `instanceof` operator. - bool InstanceOf( - const Function& constructor ///< Constructor function + /// + /// This operation can fail in case of Proxy.[[GetPrototypeOf]] calling into + /// JavaScript. + /// See + /// https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-getprototypeof + MaybeOrValue InstanceOf( + const Function& constructor ///< Constructor function ) const; template @@ -785,8 +896,16 @@ namespace Napi { T* data, Hint* finalizeHint); #if NAPI_VERSION >= 8 - bool Freeze(); - bool Seal(); + /// This operation can fail in case of Proxy.[[GetPrototypeOf]] calling into + /// JavaScript. + /// See + /// https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-getprototypeof + MaybeOrValue Freeze(); + /// This operation can fail in case of Proxy.[[GetPrototypeOf]] calling into + /// JavaScript. + /// See + /// https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-getprototypeof + MaybeOrValue Seal(); #endif // NAPI_VERSION >= 8 }; @@ -1120,30 +1239,37 @@ namespace Napi { Function(); Function(napi_env env, napi_value value); - Value operator()(const std::initializer_list& args) const; - - Value Call(const std::initializer_list& args) const; - Value Call(const std::vector& args) const; - Value Call(size_t argc, const napi_value* args) const; - Value Call(napi_value recv, - const std::initializer_list& args) const; - Value Call(napi_value recv, const std::vector& args) const; - Value Call(napi_value recv, size_t argc, const napi_value* args) const; - - Value MakeCallback(napi_value recv, - const std::initializer_list& args, - napi_async_context context = nullptr) const; - Value MakeCallback(napi_value recv, - const std::vector& args, - napi_async_context context = nullptr) const; - Value MakeCallback(napi_value recv, - size_t argc, - const napi_value* args, - napi_async_context context = nullptr) const; - - Object New(const std::initializer_list& args) const; - Object New(const std::vector& args) const; - Object New(size_t argc, const napi_value* args) const; + MaybeOrValue operator()( + const std::initializer_list& args) const; + + MaybeOrValue Call( + const std::initializer_list& args) const; + MaybeOrValue Call(const std::vector& args) const; + MaybeOrValue Call(size_t argc, const napi_value* args) const; + MaybeOrValue Call( + napi_value recv, const std::initializer_list& args) const; + MaybeOrValue Call(napi_value recv, + const std::vector& args) const; + MaybeOrValue Call(napi_value recv, + size_t argc, + const napi_value* args) const; + + MaybeOrValue MakeCallback( + napi_value recv, + const std::initializer_list& args, + napi_async_context context = nullptr) const; + MaybeOrValue MakeCallback(napi_value recv, + const std::vector& args, + napi_async_context context = nullptr) const; + MaybeOrValue MakeCallback(napi_value recv, + size_t argc, + const napi_value* args, + napi_async_context context = nullptr) const; + + MaybeOrValue New( + const std::initializer_list& args) const; + MaybeOrValue New(const std::vector& args) const; + MaybeOrValue New(size_t argc, const napi_value* args) const; }; class Promise : public Object { @@ -1267,26 +1393,26 @@ namespace Napi { ObjectReference& operator =(ObjectReference&& other); NAPI_DISALLOW_ASSIGN(ObjectReference) - Napi::Value Get(const char* utf8name) const; - Napi::Value Get(const std::string& utf8name) const; - bool Set(const char* utf8name, napi_value value); - bool Set(const char* utf8name, Napi::Value value); - bool Set(const char* utf8name, const char* utf8value); - bool Set(const char* utf8name, bool boolValue); - bool Set(const char* utf8name, double numberValue); - bool Set(const std::string& utf8name, napi_value value); - bool Set(const std::string& utf8name, Napi::Value value); - bool Set(const std::string& utf8name, std::string& utf8value); - bool Set(const std::string& utf8name, bool boolValue); - bool Set(const std::string& utf8name, double numberValue); - - Napi::Value Get(uint32_t index) const; - bool Set(uint32_t index, const napi_value value); - bool Set(uint32_t index, const Napi::Value value); - bool Set(uint32_t index, const char* utf8value); - bool Set(uint32_t index, const std::string& utf8value); - bool Set(uint32_t index, bool boolValue); - bool Set(uint32_t index, double numberValue); + MaybeOrValue Get(const char* utf8name) const; + MaybeOrValue Get(const std::string& utf8name) const; + MaybeOrValue Set(const char* utf8name, napi_value value); + MaybeOrValue Set(const char* utf8name, Napi::Value value); + MaybeOrValue Set(const char* utf8name, const char* utf8value); + MaybeOrValue Set(const char* utf8name, bool boolValue); + MaybeOrValue Set(const char* utf8name, double numberValue); + MaybeOrValue Set(const std::string& utf8name, napi_value value); + MaybeOrValue Set(const std::string& utf8name, Napi::Value value); + MaybeOrValue Set(const std::string& utf8name, std::string& utf8value); + MaybeOrValue Set(const std::string& utf8name, bool boolValue); + MaybeOrValue Set(const std::string& utf8name, double numberValue); + + MaybeOrValue Get(uint32_t index) const; + MaybeOrValue Set(uint32_t index, const napi_value value); + MaybeOrValue Set(uint32_t index, const Napi::Value value); + MaybeOrValue Set(uint32_t index, const char* utf8value); + MaybeOrValue Set(uint32_t index, const std::string& utf8value); + MaybeOrValue Set(uint32_t index, bool boolValue); + MaybeOrValue Set(uint32_t index, double numberValue); protected: ObjectReference(const ObjectReference&); @@ -1304,27 +1430,37 @@ namespace Napi { FunctionReference& operator =(FunctionReference&& other); NAPI_DISALLOW_ASSIGN_COPY(FunctionReference) - Napi::Value operator ()(const std::initializer_list& args) const; - - Napi::Value Call(const std::initializer_list& args) const; - Napi::Value Call(const std::vector& args) const; - Napi::Value Call(napi_value recv, const std::initializer_list& args) const; - Napi::Value Call(napi_value recv, const std::vector& args) const; - Napi::Value Call(napi_value recv, size_t argc, const napi_value* args) const; - - Napi::Value MakeCallback(napi_value recv, - const std::initializer_list& args, - napi_async_context context = nullptr) const; - Napi::Value MakeCallback(napi_value recv, - const std::vector& args, - napi_async_context context = nullptr) const; - Napi::Value MakeCallback(napi_value recv, - size_t argc, - const napi_value* args, - napi_async_context context = nullptr) const; - - Object New(const std::initializer_list& args) const; - Object New(const std::vector& args) const; + MaybeOrValue operator()( + const std::initializer_list& args) const; + + MaybeOrValue Call( + const std::initializer_list& args) const; + MaybeOrValue Call(const std::vector& args) const; + MaybeOrValue Call( + napi_value recv, const std::initializer_list& args) const; + MaybeOrValue Call(napi_value recv, + const std::vector& args) const; + MaybeOrValue Call(napi_value recv, + size_t argc, + const napi_value* args) const; + + MaybeOrValue MakeCallback( + napi_value recv, + const std::initializer_list& args, + napi_async_context context = nullptr) const; + MaybeOrValue MakeCallback( + napi_value recv, + const std::vector& args, + napi_async_context context = nullptr) const; + MaybeOrValue MakeCallback( + napi_value recv, + size_t argc, + const napi_value* args, + napi_async_context context = nullptr) const; + + MaybeOrValue New( + const std::initializer_list& args) const; + MaybeOrValue New(const std::vector& args) const; }; // Shortcuts to creating a new reference with inferred type and refcount = 0. diff --git a/test/README.md b/test/README.md new file mode 100644 index 000000000..7ea20d765 --- /dev/null +++ b/test/README.md @@ -0,0 +1,91 @@ +# Writing Tests + +There are multiple flavors of node-addon-api test builds that cover different +build flags defined in `napi.h`: + +1. c++ exceptions enabled, +2. c++ exceptions disabled, +3. c++ exceptions disabled, and `NODE_ADDON_API_ENABLE_MAYBE` defined. + +Functions in node-addon-api that call into JavaScript can have different +declared return types to reflect build flavor settings. For example, +`Napi::Object::Set` returns `bool` when `NODE_ADDON_API_ENABLE_MAYBE` +is not defined, and `Napi::Maybe` when `NODE_ADDON_API_ENABLE_MAYBE` +is defined. In source code, return type variants are defined as +`Napi::MaybeOrValue<>` to prevent the duplication of most of the code base. + +To properly test these build flavors, all values returned by a function defined +to return `Napi::MaybeOrValue<>` should be tested by using one of the following +test helpers to handle possible JavaScript exceptions. + +There are three test helper functions to conveniently convert +`Napi::MaybeOrValue<>` values to raw values. + +## MaybeUnwrap + +```cpp +template +T MaybeUnwrap(MaybeOrValue maybe); +``` + +Converts `MaybeOrValue` to `T` by checking that `MaybeOrValue` is NOT an +empty `Maybe`. + +Returns the original value if `NODE_ADDON_API_ENABLE_MAYBE` is not defined. + +Example: + +```cpp +Object obj = info[0].As(); +// we are sure the parameters should not throw +Value value = MaybeUnwrap(obj->Get("foobar")); +``` + +## MaybeUnwrapOr + +```cpp +template +T MaybeUnwrapOr(MaybeOrValue maybe, const T& default_value = T()); +``` + +Converts `MaybeOrValue` to `T` by getting the value that wrapped by the +`Maybe` or return the `default_value` if the `Maybe` is empty. + +Returns the original value if `NODE_ADDON_API_ENABLE_MAYBE` is not defined. + +Example: + +```cpp +Value CallWithArgs(const CallbackInfo& info) { + Function func = info[0].As(); + // We don't care if the operation is throwing or not, just return it back to node-addon-api + return MaybeUnwrapOr( + func.Call(std::initializer_list{info[1], info[2], info[3]})); +} +``` + +## MaybeUnwrapTo + +```cpp +template +bool MaybeUnwrapTo(MaybeOrValue maybe, T* out); +``` + +Converts `MaybeOrValue` to `T` by getting the value that wrapped by the +e`Maybe` or return `false` if the Maybe is empty + +Copies the `value` to `out` when `NODE_ADDON_API_ENABLE_MAYBE` is not defined + +Example: + +```cpp +Object opts = info[0].As(); +bool hasProperty = false; +// The check may throw, but we are going to suppress that. +if (MaybeUnwrapTo(opts.Has("blocking"), &hasProperty)) { + isBlocking = hasProperty && + MaybeUnwrap(MaybeUnwrap(opts.Get("blocking")).ToBoolean()); +} else { + env.GetAndClearPendingException(); +} +``` diff --git a/test/addon_data.cc b/test/addon_data.cc index d160a5946..bcbfc4eee 100644 --- a/test/addon_data.cc +++ b/test/addon_data.cc @@ -1,6 +1,7 @@ #if (NAPI_VERSION > 5) #include #include "napi.h" +#include "test_helper.h" // An overly elaborate way to get/set a boolean stored in the instance data: // 0. A boolean named "verbose" is stored in the instance data. The constructor @@ -42,7 +43,8 @@ class Addon { }; static Napi::Value Getter(const Napi::CallbackInfo& info) { - return info.Env().GetInstanceData()->VerboseIndicator.New({}); + return MaybeUnwrap( + info.Env().GetInstanceData()->VerboseIndicator.New({})); } static void Setter(const Napi::CallbackInfo& info) { diff --git a/test/basic_types/value.cc b/test/basic_types/value.cc index d18f8f30f..59de71202 100644 --- a/test/basic_types/value.cc +++ b/test/basic_types/value.cc @@ -1,4 +1,5 @@ #include "napi.h" +#include "test_helper.h" using namespace Napi; @@ -75,19 +76,19 @@ static Value IsExternal(const CallbackInfo& info) { } static Value ToBoolean(const CallbackInfo& info) { - return info[0].ToBoolean(); + return MaybeUnwrap(info[0].ToBoolean()); } static Value ToNumber(const CallbackInfo& info) { - return info[0].ToNumber(); + return MaybeUnwrap(info[0].ToNumber()); } static Value ToString(const CallbackInfo& info) { - return info[0].ToString(); + return MaybeUnwrap(info[0].ToString()); } static Value ToObject(const CallbackInfo& info) { - return info[0].ToObject(); + return MaybeUnwrap(info[0].ToObject()); } Object InitBasicTypesValue(Env env) { diff --git a/test/bigint.cc b/test/bigint.cc index 1f89db84a..ab62d09f9 100644 --- a/test/bigint.cc +++ b/test/bigint.cc @@ -3,6 +3,8 @@ #define NAPI_EXPERIMENTAL #include "napi.h" +#include "test_helper.h" + using namespace Napi; namespace { @@ -11,7 +13,7 @@ Value IsLossless(const CallbackInfo& info) { Env env = info.Env(); BigInt big = info[0].As(); - bool is_signed = info[1].ToBoolean().Value(); + bool is_signed = MaybeUnwrap(info[1].ToBoolean()).Value(); bool lossless; if (is_signed) { diff --git a/test/binding.cc b/test/binding.cc index 09bd110bc..605e55023 100644 --- a/test/binding.cc +++ b/test/binding.cc @@ -73,6 +73,10 @@ Object InitThunkingManual(Env env); Object InitObjectFreezeSeal(Env env); #endif +#if defined(NODE_ADDON_API_ENABLE_MAYBE) +Object InitMaybeCheck(Env env); +#endif + Object Init(Env env, Object exports) { #if (NAPI_VERSION > 5) exports.Set("addon", InitAddon(env)); @@ -151,6 +155,10 @@ Object Init(Env env, Object exports) { #if (NAPI_VERSION > 7) exports.Set("object_freeze_seal", InitObjectFreezeSeal(env)); #endif + +#if defined(NODE_ADDON_API_ENABLE_MAYBE) + exports.Set("maybe_check", InitMaybeCheck(env)); +#endif return exports; } diff --git a/test/binding.gyp b/test/binding.gyp index 5cef552c8..2d26fd528 100644 --- a/test/binding.gyp +++ b/test/binding.gyp @@ -1,6 +1,7 @@ { 'target_defaults': { 'includes': ['../common.gypi'], + 'include_dirs': ['./common'], 'variables': { 'build_sources': [ 'addon.cc', @@ -27,6 +28,7 @@ 'function.cc', 'functionreference.cc', 'handlescope.cc', + 'maybe/check.cc', 'movable_callbacks.cc', 'memory_management.cc', 'name.cc', @@ -91,6 +93,12 @@ 'includes': ['../noexcept.gypi'], 'sources': ['>@(build_sources)'] }, + { + 'target_name': 'binding_noexcept_maybe', + 'includes': ['../noexcept.gypi'], + 'sources': ['>@(build_sources)'], + 'defines': ['NODE_ADDON_API_ENABLE_MAYBE'] + }, { 'target_name': 'binding_swallowexcept', 'includes': ['../except.gypi'], diff --git a/test/common/index.js b/test/common/index.js index ab6d12bc8..2368b728d 100644 --- a/test/common/index.js +++ b/test/common/index.js @@ -81,6 +81,7 @@ exports.runTest = async function(test, buildType) { const bindings = [ `../build/${buildType}/binding.node`, `../build/${buildType}/binding_noexcept.node`, + `../build/${buildType}/binding_noexcept_maybe.node`, ].map(it => require.resolve(it)); for (const item of bindings) { @@ -95,6 +96,7 @@ exports.runTestWithBindingPath = async function(test, buildType) { const bindings = [ `../build/${buildType}/binding.node`, `../build/${buildType}/binding_noexcept.node`, + `../build/${buildType}/binding_noexcept_maybe.node`, ].map(it => require.resolve(it)); for (const item of bindings) { diff --git a/test/common/test_helper.h b/test/common/test_helper.h new file mode 100644 index 000000000..1b321a19e --- /dev/null +++ b/test/common/test_helper.h @@ -0,0 +1,61 @@ +#pragma once +#include "napi.h" + +namespace Napi { + +// Use this when a variable or parameter is unused in order to explicitly +// silence a compiler warning about that. +template +inline void USE(T&&) {} + +/** + * A test helper that converts MaybeOrValue to T by checking that + * MaybeOrValue is NOT an empty Maybe when NODE_ADDON_API_ENABLE_MAYBE is + * defined. + * + * Do nothing when NODE_ADDON_API_ENABLE_MAYBE is not defined. + */ +template +inline T MaybeUnwrap(MaybeOrValue maybe) { +#if defined(NODE_ADDON_API_ENABLE_MAYBE) + return maybe.Unwrap(); +#else + return maybe; +#endif +} + +/** + * A test helper that converts MaybeOrValue to T by getting the value that + * wrapped by the Maybe or return the default_value if the Maybe is empty when + * NODE_ADDON_API_ENABLE_MAYBE is defined. + * + * Do nothing when NODE_ADDON_API_ENABLE_MAYBE is not defined. + */ +template +inline T MaybeUnwrapOr(MaybeOrValue maybe, const T& default_value) { +#if defined(NODE_ADDON_API_ENABLE_MAYBE) + return maybe.UnwrapOr(default_value); +#else + USE(default_value); + return maybe; +#endif +} + +/** + * A test helper that converts MaybeOrValue to T by getting the value that + * wrapped by the Maybe or return false if the Maybe is empty when + * NODE_ADDON_API_ENABLE_MAYBE is defined. + * + * Copying the value to out when NODE_ADDON_API_ENABLE_MAYBE is not defined. + */ +template +inline bool MaybeUnwrapTo(MaybeOrValue maybe, T* out) { +#if defined(NODE_ADDON_API_ENABLE_MAYBE) + return maybe.UnwrapTo(out); +#else + *out = maybe; + return true; +#endif +} + +} // namespace Napi diff --git a/test/function.cc b/test/function.cc index b45e91dd1..0fab27290 100644 --- a/test/function.cc +++ b/test/function.cc @@ -1,4 +1,5 @@ #include "napi.h" +#include "test_helper.h" using namespace Napi; @@ -53,8 +54,8 @@ Value ValueCallbackWithData(const CallbackInfo& info) { Value CallWithArgs(const CallbackInfo& info) { Function func = info[0].As(); - return func.Call( - std::initializer_list{info[1], info[2], info[3]}); + return MaybeUnwrap( + func.Call(std::initializer_list{info[1], info[2], info[3]})); } Value CallWithVector(const CallbackInfo& info) { @@ -64,7 +65,7 @@ Value CallWithVector(const CallbackInfo& info) { args.push_back(info[1]); args.push_back(info[2]); args.push_back(info[3]); - return func.Call(args); + return MaybeUnwrap(func.Call(args)); } Value CallWithCStyleArray(const CallbackInfo& info) { @@ -74,7 +75,7 @@ Value CallWithCStyleArray(const CallbackInfo& info) { args.push_back(info[1]); args.push_back(info[2]); args.push_back(info[3]); - return func.Call(args.size(), args.data()); + return MaybeUnwrap(func.Call(args.size(), args.data())); } Value CallWithReceiverAndCStyleArray(const CallbackInfo& info) { @@ -85,13 +86,14 @@ Value CallWithReceiverAndCStyleArray(const CallbackInfo& info) { args.push_back(info[2]); args.push_back(info[3]); args.push_back(info[4]); - return func.Call(receiver, args.size(), args.data()); + return MaybeUnwrap(func.Call(receiver, args.size(), args.data())); } Value CallWithReceiverAndArgs(const CallbackInfo& info) { Function func = info[0].As(); Value receiver = info[1]; - return func.Call(receiver, std::initializer_list{ info[2], info[3], info[4] }); + return MaybeUnwrap(func.Call( + receiver, std::initializer_list{info[2], info[3], info[4]})); } Value CallWithReceiverAndVector(const CallbackInfo& info) { @@ -102,17 +104,19 @@ Value CallWithReceiverAndVector(const CallbackInfo& info) { args.push_back(info[2]); args.push_back(info[3]); args.push_back(info[4]); - return func.Call(receiver, args); + return MaybeUnwrap(func.Call(receiver, args)); } Value CallWithInvalidReceiver(const CallbackInfo& info) { Function func = info[0].As(); - return func.Call(Value(), std::initializer_list{}); + return MaybeUnwrapOr(func.Call(Value(), std::initializer_list{}), + Value()); } Value CallConstructorWithArgs(const CallbackInfo& info) { Function func = info[0].As(); - return func.New(std::initializer_list{ info[1], info[2], info[3] }); + return MaybeUnwrap( + func.New(std::initializer_list{info[1], info[2], info[3]})); } Value CallConstructorWithVector(const CallbackInfo& info) { @@ -122,7 +126,7 @@ Value CallConstructorWithVector(const CallbackInfo& info) { args.push_back(info[1]); args.push_back(info[2]); args.push_back(info[3]); - return func.New(args); + return MaybeUnwrap(func.New(args)); } Value CallConstructorWithCStyleArray(const CallbackInfo& info) { @@ -132,7 +136,7 @@ Value CallConstructorWithCStyleArray(const CallbackInfo& info) { args.push_back(info[1]); args.push_back(info[2]); args.push_back(info[3]); - return func.New(args.size(), args.data()); + return MaybeUnwrap(func.New(args.size(), args.data())); } void IsConstructCall(const CallbackInfo& info) { @@ -191,7 +195,7 @@ void MakeCallbackWithInvalidReceiver(const CallbackInfo& info) { Value CallWithFunctionOperator(const CallbackInfo& info) { Function func = info[0].As(); - return func({info[1], info[2], info[3]}); + return MaybeUnwrap(func({info[1], info[2], info[3]})); } } // end anonymous namespace diff --git a/test/functionreference.cc b/test/functionreference.cc index 44dec3ce7..aaa899e11 100644 --- a/test/functionreference.cc +++ b/test/functionreference.cc @@ -1,4 +1,5 @@ #include "napi.h" +#include "test_helper.h" using namespace Napi; @@ -8,7 +9,7 @@ Value Call(const CallbackInfo& info) { FunctionReference ref; ref.Reset(info[0].As()); - return ref.Call({}); + return MaybeUnwrapOr(ref.Call({}), Value()); } Value Construct(const CallbackInfo& info) { @@ -16,7 +17,7 @@ Value Construct(const CallbackInfo& info) { FunctionReference ref; ref.Reset(info[0].As()); - return ref.New({}); + return MaybeUnwrapOr(ref.New({}), Object()); } } // namespace diff --git a/test/globalObject/global_object_delete_property.cc b/test/globalObject/global_object_delete_property.cc index 8064e5864..295ed3f36 100644 --- a/test/globalObject/global_object_delete_property.cc +++ b/test/globalObject/global_object_delete_property.cc @@ -1,27 +1,31 @@ #include "napi.h" +#include "test_helper.h" using namespace Napi; Value DeletePropertyWithCStyleStringAsKey(const CallbackInfo& info) { Object globalObject = info.Env().Global(); String key = info[0].As(); - return Boolean::New(info.Env(), globalObject.Delete(key.Utf8Value().c_str())); + return Boolean::New( + info.Env(), MaybeUnwrap(globalObject.Delete(key.Utf8Value().c_str()))); } Value DeletePropertyWithCppStyleStringAsKey(const CallbackInfo& info) { Object globalObject = info.Env().Global(); String key = info[0].As(); - return Boolean::New(info.Env(), globalObject.Delete(key.Utf8Value())); + return Boolean::New(info.Env(), + MaybeUnwrap(globalObject.Delete(key.Utf8Value()))); } Value DeletePropertyWithInt32AsKey(const CallbackInfo& info) { Object globalObject = info.Env().Global(); Number key = info[0].As(); - return Boolean::New(info.Env(), globalObject.Delete(key.Uint32Value())); + return Boolean::New(info.Env(), + MaybeUnwrap(globalObject.Delete(key.Uint32Value()))); } Value DeletePropertyWithNapiValueAsKey(const CallbackInfo& info) { Object globalObject = info.Env().Global(); Name key = info[0].As(); - return Boolean::New(info.Env(), globalObject.Delete(key)); -} \ No newline at end of file + return Boolean::New(info.Env(), MaybeUnwrap(globalObject.Delete(key))); +} diff --git a/test/globalObject/global_object_get_property.cc b/test/globalObject/global_object_get_property.cc index bd402abf2..dd112043c 100644 --- a/test/globalObject/global_object_get_property.cc +++ b/test/globalObject/global_object_get_property.cc @@ -1,29 +1,30 @@ #include "napi.h" +#include "test_helper.h" using namespace Napi; Value GetPropertyWithNapiValueAsKey(const CallbackInfo& info) { Object globalObject = info.Env().Global(); Name key = info[0].As(); - return globalObject.Get(key); + return MaybeUnwrap(globalObject.Get(key)); } Value GetPropertyWithInt32AsKey(const CallbackInfo& info) { Object globalObject = info.Env().Global(); Number key = info[0].As(); - return globalObject.Get(key.Uint32Value()); + return MaybeUnwrapOr(globalObject.Get(key.Uint32Value()), Value()); } Value GetPropertyWithCStyleStringAsKey(const CallbackInfo& info) { Object globalObject = info.Env().Global(); String cStrkey = info[0].As(); - return globalObject.Get(cStrkey.Utf8Value().c_str()); + return MaybeUnwrapOr(globalObject.Get(cStrkey.Utf8Value().c_str()), Value()); } Value GetPropertyWithCppStyleStringAsKey(const CallbackInfo& info) { Object globalObject = info.Env().Global(); String cppStrKey = info[0].As(); - return globalObject.Get(cppStrKey.Utf8Value()); + return MaybeUnwrapOr(globalObject.Get(cppStrKey.Utf8Value()), Value()); } void CreateMockTestObject(const CallbackInfo& info) { diff --git a/test/globalObject/global_object_has_own_property.cc b/test/globalObject/global_object_has_own_property.cc index 1dfb4e281..89c299913 100644 --- a/test/globalObject/global_object_has_own_property.cc +++ b/test/globalObject/global_object_has_own_property.cc @@ -1,22 +1,28 @@ #include "napi.h" +#include "test_helper.h" using namespace Napi; Value HasPropertyWithCStyleStringAsKey(const CallbackInfo& info) { Object globalObject = info.Env().Global(); String key = info[0].As(); - return Boolean::New(info.Env(), - globalObject.HasOwnProperty(key.Utf8Value().c_str())); + return Boolean::New( + info.Env(), + MaybeUnwrapOr(globalObject.HasOwnProperty(key.Utf8Value().c_str()), + false)); } Value HasPropertyWithCppStyleStringAsKey(const CallbackInfo& info) { Object globalObject = info.Env().Global(); String key = info[0].As(); - return Boolean::New(info.Env(), globalObject.HasOwnProperty(key.Utf8Value())); + return Boolean::New( + info.Env(), + MaybeUnwrapOr(globalObject.HasOwnProperty(key.Utf8Value()), false)); } Value HasPropertyWithNapiValueAsKey(const CallbackInfo& info) { Object globalObject = info.Env().Global(); Name key = info[0].As(); - return Boolean::New(info.Env(), globalObject.HasOwnProperty(key)); -} \ No newline at end of file + return Boolean::New(info.Env(), + MaybeUnwrap(globalObject.HasOwnProperty(key))); +} diff --git a/test/maybe/check.cc b/test/maybe/check.cc new file mode 100644 index 000000000..d1e2261ed --- /dev/null +++ b/test/maybe/check.cc @@ -0,0 +1,23 @@ +#include "napi.h" +#if defined(NODE_ADDON_API_ENABLE_MAYBE) + +using namespace Napi; + +namespace { + +void VoidCallback(const CallbackInfo& info) { + Function fn = info[0].As(); + + Maybe it = fn.Call({}); + + it.Check(); +} + +} // end anonymous namespace + +Object InitMaybeCheck(Env env) { + Object exports = Object::New(env); + exports.Set("voidCallback", Function::New(env, VoidCallback)); + return exports; +} +#endif diff --git a/test/maybe/index.js b/test/maybe/index.js new file mode 100644 index 000000000..ad562c9c9 --- /dev/null +++ b/test/maybe/index.js @@ -0,0 +1,38 @@ +'use strict'; + +const buildType = process.config.target_defaults.default_configuration; +const assert = require('assert'); +const os = require('os'); + +const napiChild = require('../napi_child'); + +module.exports = test(require(`../build/${buildType}/binding_noexcept_maybe.node`).maybe_check); + +function test(binding) { + if (process.argv.includes('child')) { + child(binding); + return; + } + const cp = napiChild.spawn(process.execPath, [__filename, 'child'], { + stdio: ['ignore', 'inherit', 'pipe'], + }); + cp.stderr.setEncoding('utf8'); + let stderr = ''; + cp.stderr.on('data', chunk => { + stderr += chunk; + }); + cp.on('exit', (code, signal) => { + if (process.platform === 'win32') { + assert.strictEqual(code, 128 + 6 /* SIGABRT */); + } else { + assert.strictEqual(signal, 'SIGABRT'); + } + assert.ok(stderr.match(/FATAL ERROR: Napi::Maybe::Check Maybe value is Nothing./)); + }); +} + +function child(binding) { + binding.voidCallback(() => { + throw new Error('foobar'); + }) +} diff --git a/test/object/delete_property.cc b/test/object/delete_property.cc index 80caa7e31..ca69e5387 100644 --- a/test/object/delete_property.cc +++ b/test/object/delete_property.cc @@ -1,33 +1,38 @@ #include "napi.h" +#include "test_helper.h" using namespace Napi; Value DeletePropertyWithUint32(const CallbackInfo& info) { Object obj = info[0].As(); Number key = info[1].As(); - return Boolean::New(info.Env(), obj.Delete(key.Uint32Value())); + return Boolean::New(info.Env(), MaybeUnwrap(obj.Delete(key.Uint32Value()))); } Value DeletePropertyWithNapiValue(const CallbackInfo& info) { Object obj = info[0].As(); Name key = info[1].As(); - return Boolean::New(info.Env(), obj.Delete(static_cast(key))); + return Boolean::New( + info.Env(), + MaybeUnwrapOr(obj.Delete(static_cast(key)), false)); } Value DeletePropertyWithNapiWrapperValue(const CallbackInfo& info) { Object obj = info[0].As(); Name key = info[1].As(); - return Boolean::New(info.Env(), obj.Delete(key)); + return Boolean::New(info.Env(), MaybeUnwrapOr(obj.Delete(key), false)); } Value DeletePropertyWithCStyleString(const CallbackInfo& info) { Object obj = info[0].As(); String jsKey = info[1].As(); - return Boolean::New(info.Env(), obj.Delete(jsKey.Utf8Value().c_str())); + return Boolean::New( + info.Env(), MaybeUnwrapOr(obj.Delete(jsKey.Utf8Value().c_str()), false)); } Value DeletePropertyWithCppStyleString(const CallbackInfo& info) { Object obj = info[0].As(); String jsKey = info[1].As(); - return Boolean::New(info.Env(), obj.Delete(jsKey.Utf8Value())); + return Boolean::New(info.Env(), + MaybeUnwrapOr(obj.Delete(jsKey.Utf8Value()), false)); } diff --git a/test/object/get_property.cc b/test/object/get_property.cc index 8069da465..523f99199 100644 --- a/test/object/get_property.cc +++ b/test/object/get_property.cc @@ -1,33 +1,34 @@ #include "napi.h" +#include "test_helper.h" using namespace Napi; Value GetPropertyWithNapiValue(const CallbackInfo& info) { Object obj = info[0].As(); Name key = info[1].As(); - return obj.Get(static_cast(key)); + return MaybeUnwrapOr(obj.Get(static_cast(key)), Value()); } Value GetPropertyWithNapiWrapperValue(const CallbackInfo& info) { Object obj = info[0].As(); Name key = info[1].As(); - return obj.Get(key); + return MaybeUnwrapOr(obj.Get(key), Value()); } Value GetPropertyWithUint32(const CallbackInfo& info) { Object obj = info[0].As(); Number key = info[1].As(); - return obj.Get(key.Uint32Value()); + return MaybeUnwrap(obj.Get(key.Uint32Value())); } Value GetPropertyWithCStyleString(const CallbackInfo& info) { Object obj = info[0].As(); String jsKey = info[1].As(); - return obj.Get(jsKey.Utf8Value().c_str()); + return MaybeUnwrapOr(obj.Get(jsKey.Utf8Value().c_str()), Value()); } Value GetPropertyWithCppStyleString(const CallbackInfo& info) { Object obj = info[0].As(); String jsKey = info[1].As(); - return obj.Get(jsKey.Utf8Value()); + return MaybeUnwrapOr(obj.Get(jsKey.Utf8Value()), Value()); } diff --git a/test/object/has_own_property.cc b/test/object/has_own_property.cc index 7351be2c0..d7fbde98b 100644 --- a/test/object/has_own_property.cc +++ b/test/object/has_own_property.cc @@ -1,27 +1,34 @@ #include "napi.h" +#include "test_helper.h" using namespace Napi; Value HasOwnPropertyWithNapiValue(const CallbackInfo& info) { Object obj = info[0].As(); Name key = info[1].As(); - return Boolean::New(info.Env(), obj.HasOwnProperty(static_cast(key))); + return Boolean::New( + info.Env(), + MaybeUnwrapOr(obj.HasOwnProperty(static_cast(key)), false)); } Value HasOwnPropertyWithNapiWrapperValue(const CallbackInfo& info) { Object obj = info[0].As(); Name key = info[1].As(); - return Boolean::New(info.Env(), obj.HasOwnProperty(key)); + return Boolean::New(info.Env(), + MaybeUnwrapOr(obj.HasOwnProperty(key), false)); } Value HasOwnPropertyWithCStyleString(const CallbackInfo& info) { Object obj = info[0].As(); String jsKey = info[1].As(); - return Boolean::New(info.Env(), obj.HasOwnProperty(jsKey.Utf8Value().c_str())); + return Boolean::New( + info.Env(), + MaybeUnwrapOr(obj.HasOwnProperty(jsKey.Utf8Value().c_str()), false)); } Value HasOwnPropertyWithCppStyleString(const CallbackInfo& info) { Object obj = info[0].As(); String jsKey = info[1].As(); - return Boolean::New(info.Env(), obj.HasOwnProperty(jsKey.Utf8Value())); + return Boolean::New( + info.Env(), MaybeUnwrapOr(obj.HasOwnProperty(jsKey.Utf8Value()), false)); } diff --git a/test/object/has_property.cc b/test/object/has_property.cc index 669935cc1..fa410833f 100644 --- a/test/object/has_property.cc +++ b/test/object/has_property.cc @@ -1,33 +1,38 @@ #include "napi.h" +#include "test_helper.h" using namespace Napi; Value HasPropertyWithNapiValue(const CallbackInfo& info) { Object obj = info[0].As(); Name key = info[1].As(); - return Boolean::New(info.Env(), obj.Has(static_cast(key))); + return Boolean::New( + info.Env(), MaybeUnwrapOr(obj.Has(static_cast(key)), false)); } Value HasPropertyWithNapiWrapperValue(const CallbackInfo& info) { Object obj = info[0].As(); Name key = info[1].As(); - return Boolean::New(info.Env(), obj.Has(key)); + return Boolean::New(info.Env(), MaybeUnwrapOr(obj.Has(key), false)); } Value HasPropertyWithCStyleString(const CallbackInfo& info) { Object obj = info[0].As(); String jsKey = info[1].As(); - return Boolean::New(info.Env(), obj.Has(jsKey.Utf8Value().c_str())); + return Boolean::New(info.Env(), + MaybeUnwrapOr(obj.Has(jsKey.Utf8Value().c_str()), false)); } Value HasPropertyWithUint32(const CallbackInfo& info) { Object obj = info[0].As(); Number jsKey = info[1].As(); - return Boolean::New(info.Env(), obj.Has(jsKey.Uint32Value())); + return Boolean::New(info.Env(), + MaybeUnwrapOr(obj.Has(jsKey.Uint32Value()), false)); } Value HasPropertyWithCppStyleString(const CallbackInfo& info) { Object obj = info[0].As(); String jsKey = info[1].As(); - return Boolean::New(info.Env(), obj.Has(jsKey.Utf8Value())); + return Boolean::New(info.Env(), + MaybeUnwrapOr(obj.Has(jsKey.Utf8Value()), false)); } diff --git a/test/object/object.cc b/test/object/object.cc index 58c0392d8..a73b8e354 100644 --- a/test/object/object.cc +++ b/test/object/object.cc @@ -1,4 +1,5 @@ #include "napi.h" +#include "test_helper.h" using namespace Napi; @@ -95,7 +96,7 @@ Value ConstructorFromObject(const CallbackInfo& info) { Array GetPropertyNames(const CallbackInfo& info) { Object obj = info[0].As(); - Array arr = obj.GetPropertyNames(); + Array arr = MaybeUnwrap(obj.GetPropertyNames()); return arr; } @@ -255,7 +256,7 @@ Value CreateObjectUsingMagic(const CallbackInfo& info) { Value InstanceOf(const CallbackInfo& info) { Object obj = info[0].As(); Function constructor = info[1].As(); - return Boolean::New(info.Env(), obj.InstanceOf(constructor)); + return Boolean::New(info.Env(), MaybeUnwrap(obj.InstanceOf(constructor))); } Object InitObject(Env env) { diff --git a/test/object/object_freeze_seal.cc b/test/object/object_freeze_seal.cc index 51071a1c6..40aaeb467 100644 --- a/test/object/object_freeze_seal.cc +++ b/test/object/object_freeze_seal.cc @@ -1,4 +1,5 @@ #include "napi.h" +#include "test_helper.h" #if (NAPI_VERSION > 7) @@ -6,12 +7,12 @@ using namespace Napi; Value Freeze(const CallbackInfo& info) { Object obj = info[0].As(); - return Boolean::New(info.Env(), obj.Freeze()); + return Boolean::New(info.Env(), MaybeUnwrapOr(obj.Freeze(), false)); } Value Seal(const CallbackInfo& info) { Object obj = info[0].As(); - return Boolean::New(info.Env(), obj.Seal()); + return Boolean::New(info.Env(), MaybeUnwrapOr(obj.Seal(), false)); } Object InitObjectFreezeSeal(Env env) { diff --git a/test/object/set_property.cc b/test/object/set_property.cc index c17366544..5f10b4f09 100644 --- a/test/object/set_property.cc +++ b/test/object/set_property.cc @@ -1,4 +1,5 @@ #include "napi.h" +#include "test_helper.h" using namespace Napi; @@ -6,26 +7,31 @@ Value SetPropertyWithNapiValue(const CallbackInfo& info) { Object obj = info[0].As(); Name key = info[1].As(); Value value = info[2]; - return Boolean::New(info.Env(), obj.Set(static_cast(key), value)); + return Boolean::New( + info.Env(), + MaybeUnwrapOr(obj.Set(static_cast(key), value), false)); } Value SetPropertyWithNapiWrapperValue(const CallbackInfo& info) { Object obj = info[0].As(); Name key = info[1].As(); Value value = info[2]; - return Boolean::New(info.Env(), obj.Set(key, value)); + return Boolean::New(info.Env(), MaybeUnwrapOr(obj.Set(key, value), false)); } Value SetPropertyWithCStyleString(const CallbackInfo& info) { Object obj = info[0].As(); String jsKey = info[1].As(); Value value = info[2]; - return Boolean::New(info.Env(), obj.Set(jsKey.Utf8Value().c_str(), value)); + return Boolean::New( + info.Env(), + MaybeUnwrapOr(obj.Set(jsKey.Utf8Value().c_str(), value), false)); } Value SetPropertyWithCppStyleString(const CallbackInfo& info) { Object obj = info[0].As(); String jsKey = info[1].As(); Value value = info[2]; - return Boolean::New(info.Env(), obj.Set(jsKey.Utf8Value(), value)); + return Boolean::New(info.Env(), + MaybeUnwrapOr(obj.Set(jsKey.Utf8Value(), value), false)); } diff --git a/test/objectreference.cc b/test/objectreference.cc index 3143216dc..34f952088 100644 --- a/test/objectreference.cc +++ b/test/objectreference.cc @@ -4,6 +4,7 @@ it. Subclasses of Objects can only be set using an ObjectReference by first casting it as an Object. */ #include "napi.h" +#include "test_helper.h" using namespace Napi; @@ -97,22 +98,22 @@ Value GetFromGetter(const CallbackInfo& info) { return String::New(env, "No Referenced Value"); } else { if (info[1].IsString()) { - return weak.Get(info[1].As().Utf8Value()); + return MaybeUnwrap(weak.Get(info[1].As().Utf8Value())); } else if (info[1].IsNumber()) { - return weak.Get(info[1].As().Uint32Value()); + return MaybeUnwrap(weak.Get(info[1].As().Uint32Value())); } } } else if (info[0].As() == String::New(env, "persistent")) { if (info[1].IsString()) { - return persistent.Get(info[1].As().Utf8Value()); + return MaybeUnwrap(persistent.Get(info[1].As().Utf8Value())); } else if (info[1].IsNumber()) { - return persistent.Get(info[1].As().Uint32Value()); + return MaybeUnwrap(persistent.Get(info[1].As().Uint32Value())); } } else { if (info[0].IsString()) { - return reference.Get(info[0].As().Utf8Value()); + return MaybeUnwrap(reference.Get(info[0].As().Utf8Value())); } else if (info[0].IsNumber()) { - return reference.Get(info[0].As().Uint32Value()); + return MaybeUnwrap(reference.Get(info[0].As().Uint32Value())); } } @@ -147,12 +148,12 @@ Value GetCastedFromGetter(const CallbackInfo& info) { if (casted_weak.IsEmpty()) { return String::New(env, "No Referenced Value"); } else { - return casted_weak.Get(info[1].As()); + return MaybeUnwrap(casted_weak.Get(info[1].As())); } } else if (info[0].As() == String::New(env, "persistent")) { - return casted_persistent.Get(info[1].As()); + return MaybeUnwrap(casted_persistent.Get(info[1].As())); } else { - return casted_reference.Get(info[1].As()); + return MaybeUnwrap(casted_reference.Get(info[1].As())); } } diff --git a/test/objectwrap.cc b/test/objectwrap.cc index 2ffc85a25..65727a3df 100644 --- a/test/objectwrap.cc +++ b/test/objectwrap.cc @@ -1,9 +1,10 @@ #include +#include "test_helper.h" Napi::ObjectReference testStaticContextRef; Napi::Value StaticGetter(const Napi::CallbackInfo& /*info*/) { - return testStaticContextRef.Value().Get("value"); + return MaybeUnwrap(testStaticContextRef.Value().Get("value")); } void StaticSetter(const Napi::CallbackInfo& /*info*/, const Napi::Value& value) { @@ -11,12 +12,12 @@ void StaticSetter(const Napi::CallbackInfo& /*info*/, const Napi::Value& value) } Napi::Value TestStaticMethod(const Napi::CallbackInfo& info) { - std::string str = info[0].ToString(); + std::string str = MaybeUnwrap(info[0].ToString()); return Napi::String::New(info.Env(), str + " static"); } Napi::Value TestStaticMethodInternal(const Napi::CallbackInfo& info) { - std::string str = info[0].ToString(); + std::string str = MaybeUnwrap(info[0].ToString()); return Napi::String::New(info.Env(), str + " static internal"); } @@ -55,7 +56,7 @@ class Test : public Napi::ObjectWrap { } void Setter(const Napi::CallbackInfo& /*info*/, const Napi::Value& value) { - value_ = value.ToString(); + value_ = MaybeUnwrap(value.ToString()); } Napi::Value Getter(const Napi::CallbackInfo& info) { @@ -63,12 +64,12 @@ class Test : public Napi::ObjectWrap { } Napi::Value TestMethod(const Napi::CallbackInfo& info) { - std::string str = info[0].ToString(); + std::string str = MaybeUnwrap(info[0].ToString()); return Napi::String::New(info.Env(), str + " instance"); } Napi::Value TestMethodInternal(const Napi::CallbackInfo& info) { - std::string str = info[0].ToString(); + std::string str = MaybeUnwrap(info[0].ToString()); return Napi::String::New(info.Env(), str + " instance internal"); } @@ -80,11 +81,15 @@ class Test : public Napi::ObjectWrap { Napi::Value Iterator(const Napi::CallbackInfo& info) { Napi::Array array = Napi::Array::New(info.Env()); array.Set(array.Length(), Napi::String::From(info.Env(), value_)); - return array.Get(Napi::Symbol::WellKnown(info.Env(), "iterator")).As().Call(array, {}); + return MaybeUnwrap( + MaybeUnwrap(array.Get(MaybeUnwrap( + Napi::Symbol::WellKnown(info.Env(), "iterator")))) + .As() + .Call(array, {})); } void TestVoidMethodT(const Napi::CallbackInfo &info) { - value_ = info[0].ToString(); + value_ = MaybeUnwrap(info[0].ToString()); } Napi::Value TestMethodT(const Napi::CallbackInfo &info) { @@ -96,7 +101,7 @@ class Test : public Napi::ObjectWrap { } static void TestStaticVoidMethodT(const Napi::CallbackInfo& info) { - s_staticMethodText = info[0].ToString(); + s_staticMethodText = MaybeUnwrap(info[0].ToString()); } static void Initialize(Napi::Env env, Napi::Object exports) { @@ -115,64 +120,120 @@ class Test : public Napi::ObjectWrap { Napi::Symbol kTestMethodTInternal = Napi::Symbol::New(env, "kTestMethodTInternal"); Napi::Symbol kTestVoidMethodTInternal = Napi::Symbol::New(env, "kTestVoidMethodTInternal"); - exports.Set("Test", DefineClass(env, "Test", { - - // expose symbols for testing - StaticValue("kTestStaticValueInternal", kTestStaticValueInternal), - StaticValue("kTestStaticAccessorInternal", kTestStaticAccessorInternal), - StaticValue("kTestStaticAccessorTInternal", kTestStaticAccessorTInternal), - StaticValue("kTestStaticMethodInternal", kTestStaticMethodInternal), - StaticValue("kTestStaticMethodTInternal", kTestStaticMethodTInternal), - StaticValue("kTestStaticVoidMethodTInternal", kTestStaticVoidMethodTInternal), - StaticValue("kTestValueInternal", kTestValueInternal), - StaticValue("kTestAccessorInternal", kTestAccessorInternal), - StaticValue("kTestAccessorTInternal", kTestAccessorTInternal), - StaticValue("kTestMethodInternal", kTestMethodInternal), - StaticValue("kTestMethodTInternal", kTestMethodTInternal), - StaticValue("kTestVoidMethodTInternal", kTestVoidMethodTInternal), - - // test data - StaticValue("testStaticValue", Napi::String::New(env, "value"), napi_enumerable), - StaticValue(kTestStaticValueInternal, Napi::Number::New(env, 5), napi_default), - - StaticAccessor("testStaticGetter", &StaticGetter, nullptr, napi_enumerable), - StaticAccessor("testStaticSetter", nullptr, &StaticSetter, napi_default), - StaticAccessor("testStaticGetSet", &StaticGetter, &StaticSetter, napi_enumerable), - StaticAccessor(kTestStaticAccessorInternal, &StaticGetter, &StaticSetter, napi_enumerable), - StaticAccessor<&StaticGetter>("testStaticGetterT"), - StaticAccessor<&StaticGetter, &StaticSetter>("testStaticGetSetT"), - StaticAccessor<&StaticGetter, &StaticSetter>(kTestStaticAccessorTInternal), - - StaticMethod("testStaticMethod", &TestStaticMethod, napi_enumerable), - StaticMethod(kTestStaticMethodInternal, &TestStaticMethodInternal, napi_default), - StaticMethod<&TestStaticVoidMethodT>("testStaticVoidMethodT"), - StaticMethod<&TestStaticMethodT>("testStaticMethodT"), - StaticMethod<&TestStaticVoidMethodT>(kTestStaticVoidMethodTInternal), - StaticMethod<&TestStaticMethodT>(kTestStaticMethodTInternal), - - InstanceValue("testValue", Napi::Boolean::New(env, true), napi_enumerable), - InstanceValue(kTestValueInternal, Napi::Boolean::New(env, false), napi_enumerable), - - InstanceAccessor("testGetter", &Test::Getter, nullptr, napi_enumerable), - InstanceAccessor("testSetter", nullptr, &Test::Setter, napi_default), - InstanceAccessor("testGetSet", &Test::Getter, &Test::Setter, napi_enumerable), - InstanceAccessor(kTestAccessorInternal, &Test::Getter, &Test::Setter, napi_enumerable), - InstanceAccessor<&Test::Getter>("testGetterT"), - InstanceAccessor<&Test::Getter, &Test::Setter>("testGetSetT"), - InstanceAccessor<&Test::Getter, &Test::Setter>(kTestAccessorInternal), - - InstanceMethod("testMethod", &Test::TestMethod, napi_enumerable), - InstanceMethod(kTestMethodInternal, &Test::TestMethodInternal, napi_default), - InstanceMethod<&Test::TestMethodT>("testMethodT"), - InstanceMethod<&Test::TestVoidMethodT>("testVoidMethodT"), - InstanceMethod<&Test::TestMethodT>(kTestMethodTInternal), - InstanceMethod<&Test::TestVoidMethodT>(kTestVoidMethodTInternal), - - // conventions - InstanceAccessor(Napi::Symbol::WellKnown(env, "toStringTag"), &Test::ToStringTag, nullptr, napi_enumerable), - InstanceMethod(Napi::Symbol::WellKnown(env, "iterator"), &Test::Iterator, napi_default), - - })); + exports.Set( + "Test", + DefineClass( + env, + "Test", + { + + // expose symbols for testing + StaticValue("kTestStaticValueInternal", + kTestStaticValueInternal), + StaticValue("kTestStaticAccessorInternal", + kTestStaticAccessorInternal), + StaticValue("kTestStaticAccessorTInternal", + kTestStaticAccessorTInternal), + StaticValue("kTestStaticMethodInternal", + kTestStaticMethodInternal), + StaticValue("kTestStaticMethodTInternal", + kTestStaticMethodTInternal), + StaticValue("kTestStaticVoidMethodTInternal", + kTestStaticVoidMethodTInternal), + StaticValue("kTestValueInternal", kTestValueInternal), + StaticValue("kTestAccessorInternal", kTestAccessorInternal), + StaticValue("kTestAccessorTInternal", kTestAccessorTInternal), + StaticValue("kTestMethodInternal", kTestMethodInternal), + StaticValue("kTestMethodTInternal", kTestMethodTInternal), + StaticValue("kTestVoidMethodTInternal", + kTestVoidMethodTInternal), + + // test data + StaticValue("testStaticValue", + Napi::String::New(env, "value"), + napi_enumerable), + StaticValue(kTestStaticValueInternal, + Napi::Number::New(env, 5), + napi_default), + + StaticAccessor("testStaticGetter", + &StaticGetter, + nullptr, + napi_enumerable), + StaticAccessor( + "testStaticSetter", nullptr, &StaticSetter, napi_default), + StaticAccessor("testStaticGetSet", + &StaticGetter, + &StaticSetter, + napi_enumerable), + StaticAccessor(kTestStaticAccessorInternal, + &StaticGetter, + &StaticSetter, + napi_enumerable), + StaticAccessor<&StaticGetter>("testStaticGetterT"), + StaticAccessor<&StaticGetter, &StaticSetter>( + "testStaticGetSetT"), + StaticAccessor<&StaticGetter, &StaticSetter>( + kTestStaticAccessorTInternal), + + StaticMethod( + "testStaticMethod", &TestStaticMethod, napi_enumerable), + StaticMethod(kTestStaticMethodInternal, + &TestStaticMethodInternal, + napi_default), + StaticMethod<&TestStaticVoidMethodT>("testStaticVoidMethodT"), + StaticMethod<&TestStaticMethodT>("testStaticMethodT"), + StaticMethod<&TestStaticVoidMethodT>( + kTestStaticVoidMethodTInternal), + StaticMethod<&TestStaticMethodT>(kTestStaticMethodTInternal), + + InstanceValue("testValue", + Napi::Boolean::New(env, true), + napi_enumerable), + InstanceValue(kTestValueInternal, + Napi::Boolean::New(env, false), + napi_enumerable), + + InstanceAccessor( + "testGetter", &Test::Getter, nullptr, napi_enumerable), + InstanceAccessor( + "testSetter", nullptr, &Test::Setter, napi_default), + InstanceAccessor("testGetSet", + &Test::Getter, + &Test::Setter, + napi_enumerable), + InstanceAccessor(kTestAccessorInternal, + &Test::Getter, + &Test::Setter, + napi_enumerable), + InstanceAccessor<&Test::Getter>("testGetterT"), + InstanceAccessor<&Test::Getter, &Test::Setter>("testGetSetT"), + InstanceAccessor<&Test::Getter, &Test::Setter>( + kTestAccessorInternal), + + InstanceMethod( + "testMethod", &Test::TestMethod, napi_enumerable), + InstanceMethod(kTestMethodInternal, + &Test::TestMethodInternal, + napi_default), + InstanceMethod<&Test::TestMethodT>("testMethodT"), + InstanceMethod<&Test::TestVoidMethodT>("testVoidMethodT"), + InstanceMethod<&Test::TestMethodT>(kTestMethodTInternal), + InstanceMethod<&Test::TestVoidMethodT>( + kTestVoidMethodTInternal), + + // conventions + InstanceAccessor( + MaybeUnwrap(Napi::Symbol::WellKnown(env, "toStringTag")), + &Test::ToStringTag, + nullptr, + napi_enumerable), + InstanceMethod( + MaybeUnwrap(Napi::Symbol::WellKnown(env, "iterator")), + &Test::Iterator, + napi_default), + + })); } void Finalize(Napi::Env env) { diff --git a/test/run_script.cc b/test/run_script.cc index af47ae1f7..507164cad 100644 --- a/test/run_script.cc +++ b/test/run_script.cc @@ -1,4 +1,5 @@ #include "napi.h" +#include "test_helper.h" using namespace Napi; @@ -6,39 +7,39 @@ namespace { Value RunPlainString(const CallbackInfo& info) { Env env = info.Env(); - return env.RunScript("1 + 2 + 3"); + return MaybeUnwrap(env.RunScript("1 + 2 + 3")); } Value RunStdString(const CallbackInfo& info) { Env env = info.Env(); std::string str = "1 + 2 + 3"; - return env.RunScript(str); + return MaybeUnwrap(env.RunScript(str)); } Value RunJsString(const CallbackInfo& info) { Env env = info.Env(); - return env.RunScript(info[0].As()); + return MaybeUnwrapOr(env.RunScript(info[0].As()), Value()); } Value RunWithContext(const CallbackInfo& info) { Env env = info.Env(); - Array keys = info[1].As().GetPropertyNames(); + Array keys = MaybeUnwrap(info[1].As().GetPropertyNames()); std::string code = "("; for (unsigned int i = 0; i < keys.Length(); i++) { if (i != 0) code += ","; - code += keys.Get(i).As().Utf8Value(); + code += MaybeUnwrap(keys.Get(i)).As().Utf8Value(); } code += ") => " + info[0].As().Utf8Value(); - Value ret = env.RunScript(code); + Value ret = MaybeUnwrap(env.RunScript(code)); Function fn = ret.As(); std::vector args; for (unsigned int i = 0; i < keys.Length(); i++) { - Value key = keys.Get(i); - args.push_back(info[1].As().Get(key)); + Value key = MaybeUnwrap(keys.Get(i)); + args.push_back(MaybeUnwrap(info[1].As().Get(key))); } - return fn.Call(args); + return MaybeUnwrap(fn.Call(args)); } } // end anonymous namespace diff --git a/test/symbol.cc b/test/symbol.cc index 8fdebce6d..08ea80393 100644 --- a/test/symbol.cc +++ b/test/symbol.cc @@ -1,4 +1,5 @@ #include +#include "test_helper.h" using namespace Napi; Symbol CreateNewSymbolWithNoArgs(const Napi::CallbackInfo&) { @@ -22,33 +23,34 @@ Symbol CreateNewSymbolWithNapiString(const Napi::CallbackInfo& info) { Symbol GetWellknownSymbol(const Napi::CallbackInfo& info) { String registrySymbol = info[0].As(); - return Napi::Symbol::WellKnown(info.Env(), - registrySymbol.Utf8Value().c_str()); + return MaybeUnwrap( + Napi::Symbol::WellKnown(info.Env(), registrySymbol.Utf8Value().c_str())); } Symbol FetchSymbolFromGlobalRegistry(const Napi::CallbackInfo& info) { String registrySymbol = info[0].As(); - return Napi::Symbol::For(info.Env(), registrySymbol); + return MaybeUnwrap(Napi::Symbol::For(info.Env(), registrySymbol)); } Symbol FetchSymbolFromGlobalRegistryWithCppKey(const Napi::CallbackInfo& info) { String cppStringKey = info[0].As(); - return Napi::Symbol::For(info.Env(), cppStringKey.Utf8Value()); + return MaybeUnwrap(Napi::Symbol::For(info.Env(), cppStringKey.Utf8Value())); } Symbol FetchSymbolFromGlobalRegistryWithCKey(const Napi::CallbackInfo& info) { String cppStringKey = info[0].As(); - return Napi::Symbol::For(info.Env(), cppStringKey.Utf8Value().c_str()); + return MaybeUnwrap( + Napi::Symbol::For(info.Env(), cppStringKey.Utf8Value().c_str())); } Symbol TestUndefinedSymbolsCanBeCreated(const Napi::CallbackInfo& info) { Napi::Env env = info.Env(); - return Napi::Symbol::For(env, env.Undefined()); + return MaybeUnwrap(Napi::Symbol::For(env, env.Undefined())); } Symbol TestNullSymbolsCanBeCreated(const Napi::CallbackInfo& info) { Napi::Env env = info.Env(); - return Napi::Symbol::For(env, env.Null()); + return MaybeUnwrap(Napi::Symbol::For(env, env.Null())); } Object InitSymbol(Env env) { diff --git a/test/threadsafe_function/threadsafe_function_existing_tsfn.cc b/test/threadsafe_function/threadsafe_function_existing_tsfn.cc index 19971b824..9307b22f1 100644 --- a/test/threadsafe_function/threadsafe_function_existing_tsfn.cc +++ b/test/threadsafe_function/threadsafe_function_existing_tsfn.cc @@ -1,5 +1,6 @@ -#include "napi.h" #include +#include "napi.h" +#include "test_helper.h" #if (NAPI_VERSION > 3) @@ -59,11 +60,13 @@ static Value TestCall(const CallbackInfo &info) { bool hasData = false; if (info.Length() > 0) { Object opts = info[0].As(); - if (opts.Has("blocking")) { - isBlocking = opts.Get("blocking").ToBoolean(); + bool hasProperty = MaybeUnwrap(opts.Has("blocking")); + if (hasProperty) { + isBlocking = MaybeUnwrap(MaybeUnwrap(opts.Get("blocking")).ToBoolean()); } - if (opts.Has("data")) { - hasData = opts.Get("data").ToBoolean(); + hasProperty = MaybeUnwrap(opts.Has("data")); + if (hasProperty) { + hasData = MaybeUnwrap(MaybeUnwrap(opts.Get("data")).ToBoolean()); } } diff --git a/test/threadsafe_function/threadsafe_function_unref.cc b/test/threadsafe_function/threadsafe_function_unref.cc index 6877e50f6..a54620c48 100644 --- a/test/threadsafe_function/threadsafe_function_unref.cc +++ b/test/threadsafe_function/threadsafe_function_unref.cc @@ -1,4 +1,5 @@ #include "napi.h" +#include "test_helper.h" #if (NAPI_VERSION > 3) @@ -11,7 +12,7 @@ static Value TestUnref(const CallbackInfo& info) { Object global = env.Global(); Object resource = info[0].As(); Function cb = info[1].As(); - Function setTimeout = global.Get("setTimeout").As(); + Function setTimeout = MaybeUnwrap(global.Get("setTimeout")).As(); ThreadSafeFunction* tsfn = new ThreadSafeFunction; *tsfn = ThreadSafeFunction::New(info.Env(), cb, resource, "Test", 1, 1, [tsfn](Napi::Env /* env */) { diff --git a/test/typed_threadsafe_function/typed_threadsafe_function_existing_tsfn.cc b/test/typed_threadsafe_function/typed_threadsafe_function_existing_tsfn.cc index eccf87c93..daa273fcb 100644 --- a/test/typed_threadsafe_function/typed_threadsafe_function_existing_tsfn.cc +++ b/test/typed_threadsafe_function/typed_threadsafe_function_existing_tsfn.cc @@ -1,5 +1,6 @@ #include #include "napi.h" +#include "test_helper.h" #if (NAPI_VERSION > 3) @@ -64,11 +65,13 @@ static Value TestCall(const CallbackInfo& info) { bool hasData = false; if (info.Length() > 0) { Object opts = info[0].As(); - if (opts.Has("blocking")) { - isBlocking = opts.Get("blocking").ToBoolean(); + bool hasProperty = MaybeUnwrap(opts.Has("blocking")); + if (hasProperty) { + isBlocking = MaybeUnwrap(MaybeUnwrap(opts.Get("blocking")).ToBoolean()); } - if (opts.Has("data")) { - hasData = opts.Get("data").ToBoolean(); + hasProperty = MaybeUnwrap(opts.Has("data")); + if (hasProperty) { + hasData = MaybeUnwrap(MaybeUnwrap(opts.Get("data")).ToBoolean()); } } diff --git a/test/typed_threadsafe_function/typed_threadsafe_function_unref.cc b/test/typed_threadsafe_function/typed_threadsafe_function_unref.cc index 35345568d..2d137328e 100644 --- a/test/typed_threadsafe_function/typed_threadsafe_function_unref.cc +++ b/test/typed_threadsafe_function/typed_threadsafe_function_unref.cc @@ -1,4 +1,5 @@ #include "napi.h" +#include "test_helper.h" #if (NAPI_VERSION > 3) @@ -14,7 +15,7 @@ static Value TestUnref(const CallbackInfo& info) { Object global = env.Global(); Object resource = info[0].As(); Function cb = info[1].As(); - Function setTimeout = global.Get("setTimeout").As(); + Function setTimeout = MaybeUnwrap(global.Get("setTimeout")).As(); TSFN* tsfn = new TSFN; *tsfn = TSFN::New(