From 451f27f11fb916b7cff37457297cd229c93e3ad7 Mon Sep 17 00:00:00 2001 From: legendecas Date: Tue, 9 Mar 2021 21:14:49 +0800 Subject: [PATCH 01/10] src: return Maybe<> on pending exception when cpp exception disabled --- doc/error_handling.md | 72 ++- doc/maybe.md | 74 +++ doc/setup.md | 9 + napi-inl.h | 565 ++++++++++++------ napi.h | 449 +++++++++----- test/addon_data.cc | 4 +- test/basic_types/value.cc | 9 +- test/bigint.cc | 4 +- test/binding.cc | 8 + test/binding.gyp | 7 + test/common/index.js | 2 + test/common/test_helper.h | 61 ++ test/function.cc | 28 +- .../global_object_delete_property.cc | 14 +- .../global_object_get_property.cc | 9 +- .../global_object_has_own_property.cc | 14 +- test/maybe/check.cc | 23 + test/maybe/index.js | 38 ++ test/object/delete_property.cc | 13 +- test/object/get_property.cc | 11 +- test/object/has_own_property.cc | 13 +- test/object/has_property.cc | 13 +- test/object/object.cc | 5 +- test/object/object_freeze_seal.cc | 5 +- test/object/set_property.cc | 12 +- test/objectreference.cc | 19 +- test/objectwrap.cc | 195 +++--- test/run_script.cc | 19 +- .../threadsafe_function_existing_tsfn.cc | 18 +- .../threadsafe_function_unref.cc | 3 +- ...typed_threadsafe_function_existing_tsfn.cc | 16 +- .../typed_threadsafe_function_unref.cc | 3 +- 32 files changed, 1237 insertions(+), 498 deletions(-) create mode 100644 doc/maybe.md create mode 100644 test/common/test_helper.h create mode 100644 test/maybe/check.cc create mode 100644 test/maybe/index.js diff --git a/doc/error_handling.md b/doc/error_handling.md index 50c04a0fe..7925cc9ec 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,69 @@ 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 function do not throw a C++ exceptions. Instead, these node-api +functions that calling 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 calling into another node-api (for more info see: [Env](env.md)). + +The conversion from `Maybe` boxed values to 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 +191,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 +207,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..af63c8d4b --- /dev/null +++ b/doc/maybe.md @@ -0,0 +1,74 @@ +# Maybe (template) + +Class `Napi::Maybe` represents an maybe empty value: 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 the function may throw a +JavaScript exception and cause the program unable to evaluation any JavaScript +code until the exception is 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 if the `Maybe` is `Nothing` and does not contain a value. + +### IsJust + +```cpp +template +bool Napi::Maybe::IsJust() const; +``` + +Returns if the `Maybe` is `Just` and contains a value. + +### 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 using a default +value if this Maybe is nothing (empty). + +### UnwrapTo + +```cpp +template +bool Napi::Maybe::UnwrapTo() 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 513f4a3c8..685427370 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 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 7e9dd726a..96f0b79af 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_MAYBE_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_MAYBE_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_MAYBE_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_MAYBE_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_MAYBE_RETURN_OR_THROW_IF_FAILED( + _env, status, Napi::Object(_env, result), Napi::Object); } //////////////////////////////////////////////////////////////////////////////// @@ -773,7 +839,10 @@ inline int64_t Number::Int64Value() const { } inline float Number::FloatValue() const { - return static_cast(DoubleValue()); + double result; + napi_status status = napi_get_value_double(_env, _value, &result); + NAPI_THROW_IF_FAILED(_env, status, 0); + return result; } inline double Number::DoubleValue() const { @@ -994,8 +1063,19 @@ 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() : Name() { @@ -1110,12 +1190,25 @@ 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 + // TODO: Find a way more intuitive on maybe enabled. + 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 + // TODO: Find a way more intuitive on maybe enabled. + result.Unwrap(); +#endif return *this; } @@ -1148,208 +1241,195 @@ 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_MAYBE_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_MAYBE_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_MAYBE_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_MAYBE_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_MAYBE_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_MAYBE_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_MAYBE_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_MAYBE_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_MAYBE_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_MAYBE_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_MAYBE_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_MAYBE_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_MAYBE_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_MAYBE_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_MAYBE_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_MAYBE_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_MAYBE_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); } -inline Array Object::GetPropertyNames() const { +inline MaybeOrValue Object::GetPropertyNames() { 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_MAYBE_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_MAYBE_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_MAYBE_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_MAYBE_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) { bool result; napi_status status = napi_instanceof(_env, _value, constructor, &result); - NAPI_THROW_IF_FAILED(_env, status, false); - return result; + NAPI_MAYBE_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); } template @@ -1389,16 +1469,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_MAYBE_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_MAYBE_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); } #endif // NAPI_VERSION >= 8 @@ -2045,53 +2123,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_MAYBE_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, @@ -2099,24 +2185,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_MAYBE_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_MAYBE_RETURN_OR_THROW_IF_FAILED( + _env, status, Napi::Object(_env, result), Napi::Object); } //////////////////////////////////////////////////////////////////////////////// @@ -2388,7 +2477,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; @@ -2679,102 +2775,96 @@ inline ObjectReference::ObjectReference(const ObjectReference& other) : Reference(other) { } -inline Napi::Value ObjectReference::Get(const char* utf8name) const { - EscapableHandleScope scope(_env); - return scope.Escape(Value().Get(utf8name)); +inline MaybeOrValue ObjectReference::Get( + const char* utf8name) const { + return Value().Get(utf8name); } -inline Napi::Value ObjectReference::Get(const std::string& utf8name) const { - EscapableHandleScope scope(_env); - return scope.Escape(Value().Get(utf8name)); +inline MaybeOrValue ObjectReference::Get( + const std::string& utf8name) const { + return Value().Get(utf8name); } -inline bool ObjectReference::Set(const char* utf8name, napi_value value) { - HandleScope scope(_env); +inline MaybeOrValue ObjectReference::Set(const char* utf8name, + napi_value value) { return Value().Set(utf8name, value); } -inline bool ObjectReference::Set(const char* utf8name, Napi::Value value) { - HandleScope scope(_env); +inline MaybeOrValue ObjectReference::Set(const char* utf8name, + Napi::Value value) { return Value().Set(utf8name, value); } -inline bool ObjectReference::Set(const char* utf8name, const char* utf8value) { - HandleScope scope(_env); +inline MaybeOrValue ObjectReference::Set(const char* utf8name, + const char* utf8value) { return Value().Set(utf8name, utf8value); } -inline bool ObjectReference::Set(const char* utf8name, bool boolValue) { - HandleScope scope(_env); +inline MaybeOrValue ObjectReference::Set(const char* utf8name, + bool boolValue) { return Value().Set(utf8name, boolValue); } -inline bool ObjectReference::Set(const char* utf8name, double numberValue) { - HandleScope scope(_env); +inline MaybeOrValue ObjectReference::Set(const char* utf8name, + double numberValue) { return Value().Set(utf8name, numberValue); } -inline bool ObjectReference::Set(const std::string& utf8name, - napi_value value) { - HandleScope scope(_env); +inline MaybeOrValue ObjectReference::Set(const std::string& utf8name, + napi_value value) { return Value().Set(utf8name, value); } -inline bool ObjectReference::Set(const std::string& utf8name, - Napi::Value value) { - HandleScope scope(_env); +inline MaybeOrValue ObjectReference::Set(const std::string& utf8name, + Napi::Value value) { return Value().Set(utf8name, value); } -inline bool ObjectReference::Set(const std::string& utf8name, - std::string& utf8value) { - HandleScope scope(_env); +inline MaybeOrValue ObjectReference::Set(const std::string& utf8name, + std::string& utf8value) { return Value().Set(utf8name, utf8value); } -inline bool ObjectReference::Set(const std::string& utf8name, bool boolValue) { - HandleScope scope(_env); +inline MaybeOrValue ObjectReference::Set(const std::string& utf8name, + bool boolValue) { return Value().Set(utf8name, boolValue); } -inline bool ObjectReference::Set(const std::string& utf8name, - double numberValue) { - HandleScope scope(_env); +inline MaybeOrValue ObjectReference::Set(const std::string& utf8name, + double numberValue) { return Value().Set(utf8name, numberValue); } -inline Napi::Value ObjectReference::Get(uint32_t index) const { - EscapableHandleScope scope(_env); - return scope.Escape(Value().Get(index)); +inline MaybeOrValue ObjectReference::Get(uint32_t index) const { + return Value().Get(index); } -inline bool ObjectReference::Set(uint32_t index, napi_value value) { - HandleScope scope(_env); +inline MaybeOrValue ObjectReference::Set(uint32_t index, + napi_value value) { return Value().Set(index, value); } -inline bool ObjectReference::Set(uint32_t index, Napi::Value value) { - HandleScope scope(_env); +inline MaybeOrValue ObjectReference::Set(uint32_t index, + Napi::Value value) { return Value().Set(index, value); } -inline bool ObjectReference::Set(uint32_t index, const char* utf8value) { - HandleScope scope(_env); +inline MaybeOrValue ObjectReference::Set(uint32_t index, + const char* utf8value) { return Value().Set(index, utf8value); } -inline bool ObjectReference::Set(uint32_t index, const std::string& utf8value) { - HandleScope scope(_env); +inline MaybeOrValue ObjectReference::Set(uint32_t index, + const std::string& utf8value) { return Value().Set(index, utf8value); } -inline bool ObjectReference::Set(uint32_t index, bool boolValue) { - HandleScope scope(_env); +inline MaybeOrValue ObjectReference::Set(uint32_t index, bool boolValue) { return Value().Set(index, boolValue); } -inline bool ObjectReference::Set(uint32_t index, double numberValue) { - HandleScope scope(_env); +inline MaybeOrValue ObjectReference::Set(uint32_t index, + double numberValue) { return Value().Set(index, numberValue); } @@ -2807,105 +2897,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 7dc9b17df..e96daf685 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,42 @@ 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_pending_exception) { \ + return __VA_ARGS__; \ + } \ + 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_pending_exception) { \ + return; \ + } \ + 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_MAYBE_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_MAYBE_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 +127,26 @@ 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) + +// Annotate a function indicating the caller must examine the return value. +// Use like: +// NAPI_WARN_UNUSED_RESULT int foo(); +// TODO: find a way to define NAPI_HAS_ATTRIBUTE_WARN_UNUSED_RESULT +#if NAPI_HAS_ATTRIBUTE_WARN_UNUSED_RESULT +#define NAPI_WARN_UNUSED_RESULT __attribute__((warn_unused_result)) +#else +#define NAPI_WARN_UNUSED_RESULT /* NOT SUPPORTED */ +#endif + //////////////////////////////////////////////////////////////////////////////// /// Node-API C++ Wrapper Classes /// @@ -165,6 +207,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 +300,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 +414,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,7 +643,7 @@ 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); Symbol(); ///< Creates a new _empty_ Symbol instance. Symbol(napi_env env, @@ -583,177 +690,191 @@ namespace Napi { Object(napi_env env, napi_value value); ///< Wraps a Node-API value primitive. + /// TODO(legendecas): find a way to boxing with MaybeOrValue. /// Gets or sets a named property. PropertyLValue operator []( const char* utf8name ///< UTF-8 encoded null-terminated property name ); + /// TODO(legendecas): find a way to boxing with MaybeOrValue. /// Gets or sets a named property. PropertyLValue operator []( const std::string& utf8name ///< UTF-8 encoded property name ); + /// TODO(legendecas): find a way to boxing with MaybeOrValue. /// Gets or sets an indexed property or array element. PropertyLValue operator []( uint32_t index /// Property / element index ); + /// TODO(legendecas): find a way to boxing with MaybeOrValue. /// 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; + /// TODO(legendecas): find a way to boxing with MaybeOrValue. /// 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; + /// TODO(legendecas): find a way to boxing with MaybeOrValue. /// 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(); ///< 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 ); @@ -761,9 +882,14 @@ 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 - ) const; + /// + /// 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 + ); template inline void AddFinalizer(Finalizer finalizeCallback, T* data); @@ -773,8 +899,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 }; @@ -1108,30 +1242,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 { @@ -1255,26 +1396,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&); @@ -1292,27 +1433,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/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..563911c5f 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 MaybeUnwrapOr(info[0].ToBoolean()); } static Value ToNumber(const CallbackInfo& info) { - return info[0].ToNumber(); + return MaybeUnwrapOr(info[0].ToNumber()); } static Value ToString(const CallbackInfo& info) { - return info[0].ToString(); + return MaybeUnwrapOr(info[0].ToString()); } static Value ToObject(const CallbackInfo& info) { - return info[0].ToObject(); + return MaybeUnwrapOr(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 f450c71d2..c9e193a05 100644 --- a/test/binding.cc +++ b/test/binding.cc @@ -72,6 +72,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)); @@ -149,6 +153,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 7e6864535..d6c524036 100644 --- a/test/binding.gyp +++ b/test/binding.gyp @@ -1,6 +1,7 @@ { 'target_defaults': { 'includes': ['../common.gypi'], + 'include_dirs': ['./common'], 'sources': [ 'addon.cc', 'addon_data.cc', @@ -26,6 +27,7 @@ 'function.cc', 'functionreference.cc', 'handlescope.cc', + 'maybe/check.cc', 'movable_callbacks.cc', 'memory_management.cc', 'name.cc', @@ -82,5 +84,10 @@ 'target_name': 'binding_noexcept', 'includes': ['../noexcept.gypi'] }, + { + 'target_name': 'binding_noexcept_maybe', + 'includes': ['../noexcept.gypi'], + 'defines': ['NODE_ADDON_API_ENABLE_MAYBE'] + }, ], } 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..adb8b8f6f --- /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 = T()) { +#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 T 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..66d42a287 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 MaybeUnwrapOr( + 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 MaybeUnwrapOr(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 MaybeUnwrapOr(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 MaybeUnwrapOr(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 MaybeUnwrapOr(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 MaybeUnwrapOr(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 CallConstructorWithArgs(const CallbackInfo& info) { Function func = info[0].As(); - return func.New(std::initializer_list{ info[1], info[2], info[3] }); + return MaybeUnwrapOr( + 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 MaybeUnwrapOr(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 MaybeUnwrapOr(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 MaybeUnwrapOr(func({info[1], info[2], info[3]})); } } // end anonymous namespace diff --git a/test/globalObject/global_object_delete_property.cc b/test/globalObject/global_object_delete_property.cc index 8064e5864..7f24030f3 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(), MaybeUnwrapOr(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(), + MaybeUnwrapOr(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(), + MaybeUnwrapOr(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(), MaybeUnwrapOr(globalObject.Delete(key))); +} diff --git a/test/globalObject/global_object_get_property.cc b/test/globalObject/global_object_get_property.cc index bd402abf2..5abf9777c 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 MaybeUnwrapOr(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 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 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())); } 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..0ca95f526 100644 --- a/test/globalObject/global_object_has_own_property.cc +++ b/test/globalObject/global_object_has_own_property.cc @@ -1,22 +1,26 @@ #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()))); } 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()))); } 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(), + MaybeUnwrapOr(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..69f2c03d6 100644 --- a/test/object/delete_property.cc +++ b/test/object/delete_property.cc @@ -1,33 +1,36 @@ #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(), MaybeUnwrapOr(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)))); } 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))); } 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()))); } 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()))); } diff --git a/test/object/get_property.cc b/test/object/get_property.cc index 8069da465..c2e832013 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 GetPropertyWithNapiWrapperValue(const CallbackInfo& info) { Object obj = info[0].As(); Name key = info[1].As(); - return obj.Get(key); + return MaybeUnwrapOr(obj.Get(key)); } Value GetPropertyWithUint32(const CallbackInfo& info) { Object obj = info[0].As(); Number key = info[1].As(); - return obj.Get(key.Uint32Value()); + return MaybeUnwrapOr(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 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())); } diff --git a/test/object/has_own_property.cc b/test/object/has_own_property.cc index 7351be2c0..748df2d01 100644 --- a/test/object/has_own_property.cc +++ b/test/object/has_own_property.cc @@ -1,27 +1,32 @@ #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)))); } 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))); } 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()))); } 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()))); } diff --git a/test/object/has_property.cc b/test/object/has_property.cc index 669935cc1..e26bc240c 100644 --- a/test/object/has_property.cc +++ b/test/object/has_property.cc @@ -1,33 +1,36 @@ #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)))); } 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))); } 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()))); } 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()))); } 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()))); } diff --git a/test/object/object.cc b/test/object/object.cc index 58c0392d8..6ba12d190 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 = MaybeUnwrapOr(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(), MaybeUnwrapOr(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..b461c3ccf 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())); } 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())); } Object InitObjectFreezeSeal(Env env) { diff --git a/test/object/set_property.cc b/test/object/set_property.cc index c17366544..f7aa65807 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,29 @@ 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))); } 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))); } 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))); } 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))); } diff --git a/test/objectreference.cc b/test/objectreference.cc index 3143216dc..46fb38159 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 MaybeUnwrapOr(weak.Get(info[1].As().Utf8Value())); } else if (info[1].IsNumber()) { - return weak.Get(info[1].As().Uint32Value()); + return MaybeUnwrapOr(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 MaybeUnwrapOr(persistent.Get(info[1].As().Utf8Value())); } else if (info[1].IsNumber()) { - return persistent.Get(info[1].As().Uint32Value()); + return MaybeUnwrapOr(persistent.Get(info[1].As().Uint32Value())); } } else { if (info[0].IsString()) { - return reference.Get(info[0].As().Utf8Value()); + return MaybeUnwrapOr(reference.Get(info[0].As().Utf8Value())); } else if (info[0].IsNumber()) { - return reference.Get(info[0].As().Uint32Value()); + return MaybeUnwrapOr(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 MaybeUnwrapOr(casted_weak.Get(info[1].As())); } } else if (info[0].As() == String::New(env, "persistent")) { - return casted_persistent.Get(info[1].As()); + return MaybeUnwrapOr(casted_persistent.Get(info[1].As())); } else { - return casted_reference.Get(info[1].As()); + return MaybeUnwrapOr(casted_reference.Get(info[1].As())); } } diff --git a/test/objectwrap.cc b/test/objectwrap.cc index 2ffc85a25..2a8300a75 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 MaybeUnwrapOr(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 MaybeUnwrapOr( + 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..9ec2d7cb2 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 MaybeUnwrapOr(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 MaybeUnwrapOr(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 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 MaybeUnwrapOr(fn.Call(args)); } } // end anonymous namespace diff --git a/test/threadsafe_function/threadsafe_function_existing_tsfn.cc b/test/threadsafe_function/threadsafe_function_existing_tsfn.cc index 19971b824..126df5dde 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,18 @@ 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 = false; + if (MaybeUnwrapTo(opts.Has("blocking"), &hasProperty)) { + isBlocking = hasProperty && + MaybeUnwrap(MaybeUnwrap(opts.Get("blocking")).ToBoolean()); + } else { + env.GetAndClearPendingException(); } - if (opts.Has("data")) { - hasData = opts.Get("data").ToBoolean(); + if (MaybeUnwrapTo(opts.Has("data"), &hasProperty)) { + hasData = + hasProperty && MaybeUnwrap(MaybeUnwrap(opts.Get("data")).ToBoolean()); + } else { + env.GetAndClearPendingException(); } } 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..f2c1a1224 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,18 @@ 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 = false; + if (MaybeUnwrapTo(opts.Has("blocking"), &hasProperty)) { + isBlocking = hasProperty && + MaybeUnwrap(MaybeUnwrap(opts.Get("blocking")).ToBoolean()); + } else { + env.GetAndClearPendingException(); } - if (opts.Has("data")) { - hasData = opts.Get("data").ToBoolean(); + if (MaybeUnwrapTo(opts.Has("data"), &hasProperty)) { + hasData = + hasProperty && MaybeUnwrap(MaybeUnwrap(opts.Get("data")).ToBoolean()); + } else { + env.GetAndClearPendingException(); } } 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( From 0f95980dc1316f6769ed555e0e2abdfd3b711b3c Mon Sep 17 00:00:00 2001 From: legendecas Date: Fri, 4 Jun 2021 00:42:23 +0800 Subject: [PATCH 02/10] fixup! remove the newly added napi_pending_exception checks --- napi.h | 6 ------ 1 file changed, 6 deletions(-) diff --git a/napi.h b/napi.h index e96daf685..75f28eb39 100644 --- a/napi.h +++ b/napi.h @@ -85,18 +85,12 @@ static_assert(sizeof(char16_t) == sizeof(wchar_t), "Size mismatch between char16 } while (0) #define NAPI_THROW_IF_FAILED(env, status, ...) \ - if ((status) == napi_pending_exception) { \ - return __VA_ARGS__; \ - } \ if ((status) != napi_ok) { \ Napi::Error::New(env).ThrowAsJavaScriptException(); \ return __VA_ARGS__; \ } #define NAPI_THROW_IF_FAILED_VOID(env, status) \ - if ((status) == napi_pending_exception) { \ - return; \ - } \ if ((status) != napi_ok) { \ Napi::Error::New(env).ThrowAsJavaScriptException(); \ return; \ From 676798c3facf4417263fbfde134269bae83f0c60 Mon Sep 17 00:00:00 2001 From: legendecas Date: Wed, 9 Jun 2021 23:24:54 +0800 Subject: [PATCH 03/10] fixup! FunctionReference::New return type --- test/functionreference.cc | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/functionreference.cc b/test/functionreference.cc index 44dec3ce7..392697a54 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 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({})); } } // namespace From 6d16ddab9243cc930a8fd2d75cba7468cfc4c298 Mon Sep 17 00:00:00 2001 From: legendecas Date: Fri, 11 Jun 2021 00:18:10 +0800 Subject: [PATCH 04/10] fixup! --- napi-inl.h | 7 +------ napi.h | 42 ++++++++++++++++-------------------------- 2 files changed, 17 insertions(+), 32 deletions(-) diff --git a/napi-inl.h b/napi-inl.h index 96f0b79af..15cd4d497 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -839,10 +839,7 @@ inline int64_t Number::Int64Value() const { } inline float Number::FloatValue() const { - double result; - napi_status status = napi_get_value_double(_env, _value, &result); - NAPI_THROW_IF_FAILED(_env, status, 0); - return result; + return static_cast(DoubleValue()); } inline double Number::DoubleValue() const { @@ -1192,7 +1189,6 @@ template inline Object::PropertyLValue::operator Value() const { MaybeOrValue val = Object(_env, _object).Get(_key); #ifdef NODE_ADDON_API_ENABLE_MAYBE - // TODO: Find a way more intuitive on maybe enabled. return val.Unwrap(); #else return val; @@ -1206,7 +1202,6 @@ inline Object::PropertyLValue& Object::PropertyLValue::operator =(Valu #endif Object(_env, _object).Set(_key, value); #ifdef NODE_ADDON_API_ENABLE_MAYBE - // TODO: Find a way more intuitive on maybe enabled. result.Unwrap(); #endif return *this; diff --git a/napi.h b/napi.h index 75f28eb39..dc8f82dfb 100644 --- a/napi.h +++ b/napi.h @@ -131,16 +131,6 @@ static_assert(sizeof(char16_t) == sizeof(wchar_t), "Size mismatch between char16 #define NAPI_FATAL_IF_FAILED(status, location, message) \ NAPI_CHECK((status) == napi_ok, location, message) -// Annotate a function indicating the caller must examine the return value. -// Use like: -// NAPI_WARN_UNUSED_RESULT int foo(); -// TODO: find a way to define NAPI_HAS_ATTRIBUTE_WARN_UNUSED_RESULT -#if NAPI_HAS_ATTRIBUTE_WARN_UNUSED_RESULT -#define NAPI_WARN_UNUSED_RESULT __attribute__((warn_unused_result)) -#else -#define NAPI_WARN_UNUSED_RESULT /* NOT SUPPORTED */ -#endif - //////////////////////////////////////////////////////////////////////////////// /// Node-API C++ Wrapper Classes /// @@ -647,16 +637,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; @@ -684,37 +680,31 @@ namespace Napi { Object(napi_env env, napi_value value); ///< Wraps a Node-API value primitive. - /// TODO(legendecas): find a way to boxing with MaybeOrValue. /// Gets or sets a named property. PropertyLValue operator []( const char* utf8name ///< UTF-8 encoded null-terminated property name ); - /// TODO(legendecas): find a way to boxing with MaybeOrValue. /// Gets or sets a named property. PropertyLValue operator []( const std::string& utf8name ///< UTF-8 encoded property name ); - /// TODO(legendecas): find a way to boxing with MaybeOrValue. /// Gets or sets an indexed property or array element. PropertyLValue operator []( uint32_t index /// Property / element index ); - /// TODO(legendecas): find a way to boxing with MaybeOrValue. /// Gets a named property. MaybeOrValue operator[]( const char* utf8name ///< UTF-8 encoded null-terminated property name ) const; - /// TODO(legendecas): find a way to boxing with MaybeOrValue. /// Gets a named property. MaybeOrValue operator[]( const std::string& utf8name ///< UTF-8 encoded property name ) const; - /// TODO(legendecas): find a way to boxing with MaybeOrValue. /// Gets an indexed property or array element. MaybeOrValue operator[](uint32_t index ///< Property / element index ) const; From c1385ba923d1d0a1f238cf75832917f6acfbaf2f Mon Sep 17 00:00:00 2001 From: legendecas Date: Fri, 11 Jun 2021 00:44:19 +0800 Subject: [PATCH 05/10] fixup! doc: test docs --- test/README.md | 102 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 test/README.md diff --git a/test/README.md b/test/README.md new file mode 100644 index 000000000..64f09180c --- /dev/null +++ b/test/README.md @@ -0,0 +1,102 @@ +# Writing Tests + +There are multiple flavors of node-addon-api test build that covers 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` is defined. + +`Napi` functions that calling into JavaScript can have different declared +`NODE_ADDON_API_ENABLE_MAYBE` is defined and c++ exceptions disabled +return types to reflect build flavor settings. For example, +`Napi::Object::Set` returns `bool`, and `Napi::Maybe` when +`NODE_ADDON_API_ENABLE_MAYBE` is defined. In source code, return type variants +are defined as `Napi::MaybeOrValue<>` to prevent from duplicating most of the +code base. + +To properly test these build flavors, we should take care of the return value +of `Napi` functions that may call into JavaScript. For example: + +```cpp +#include "napi.h" +#include "test_helper.h" + +using namespace Napi; + +Value fn(const CallbackInfo& info) { + Object obj = info[0].As(); + Value value = MaybeUnwrap(obj->Get("foobar")); // <- `obj->Get` may throws +} +``` + +There are there test helper function to conveniently convert `Napi::MaybeOrValue<>` +type to raw types. + +## 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(); +Value value = MaybeUnwrap(obj->Get("foobar")); // we are sure the parameters should not throw +``` + +## 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 +T 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 throwing, but we are going to suppress that. +if (MaybeUnwrapTo(opts.Has("blocking"), &hasProperty)) { + isBlocking = hasProperty && + MaybeUnwrap(MaybeUnwrap(opts.Get("blocking")).ToBoolean()); +} else { + env.GetAndClearPendingException(); +} +``` From b946305a05637ef804c7cd8a84e23bfc1ce92e3e Mon Sep 17 00:00:00 2001 From: legendecas Date: Fri, 11 Jun 2021 00:46:28 +0800 Subject: [PATCH 06/10] fixup! linter complaints --- napi.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/napi.h b/napi.h index dc8f82dfb..c0ef9a24d 100644 --- a/napi.h +++ b/napi.h @@ -670,7 +670,7 @@ namespace Napi { Key _key; friend class Napi::Object; - }; + }; /// Creates a new Object value. static Object New(napi_env env ///< Node-API environment From fec9561e9e14555090f6b98349d0a5bf2a5405c8 Mon Sep 17 00:00:00 2001 From: legendecas Date: Wed, 16 Jun 2021 22:41:01 +0800 Subject: [PATCH 07/10] fixup! review suggestions --- doc/error_handling.md | 2 +- doc/maybe.md | 14 ++--- napi-inl.h | 132 ++++++++++++++++++++++++++++-------------- napi.h | 8 +-- test/README.md | 31 +++++----- 5 files changed, 120 insertions(+), 67 deletions(-) diff --git a/doc/error_handling.md b/doc/error_handling.md index 7925cc9ec..639cbd64f 100644 --- a/doc/error_handling.md +++ b/doc/error_handling.md @@ -104,7 +104,7 @@ exception. 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 function do not throw a C++ exceptions. Instead, these node-api -functions that calling into JavaScript are returning with `Maybe` boxed values. +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 diff --git a/doc/maybe.md b/doc/maybe.md index af63c8d4b..c03387f60 100644 --- a/doc/maybe.md +++ b/doc/maybe.md @@ -1,10 +1,10 @@ # Maybe (template) -Class `Napi::Maybe` represents an maybe empty value: 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 the function may throw a -JavaScript exception and cause the program unable to evaluation any JavaScript -code until the exception is been handled. +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. @@ -58,7 +58,7 @@ template T Napi::Maybe::UnwrapOr(const T& default_value) const; ``` -Return the value of type T contained in the Maybe, or using a default +Return the value of type T contained in the Maybe, or use a default value if this Maybe is nothing (empty). ### UnwrapTo @@ -69,6 +69,6 @@ bool Napi::Maybe::UnwrapTo() 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. +nothing (empty), `false` is returned and `out` is left untouched. [`Napi::Value`]: ./value.md diff --git a/napi-inl.h b/napi-inl.h index 15cd4d497..f33630220 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -504,7 +504,7 @@ inline MaybeOrValue Env::RunScript(const std::string& utf8script) { inline MaybeOrValue Env::RunScript(String script) { napi_value result; napi_status status = napi_run_script(_env, script, &result); - NAPI_MAYBE_RETURN_OR_THROW_IF_FAILED( + NAPI_RETURN_OR_THROW_IF_FAILED( _env, status, Napi::Value(_env, result), Napi::Value); } @@ -727,28 +727,28 @@ inline T Value::As() const { inline MaybeOrValue Value::ToBoolean() const { napi_value result; napi_status status = napi_coerce_to_bool(_env, _value, &result); - NAPI_MAYBE_RETURN_OR_THROW_IF_FAILED( + NAPI_RETURN_OR_THROW_IF_FAILED( _env, status, Napi::Boolean(_env, result), Napi::Boolean); } inline MaybeOrValue Value::ToNumber() const { napi_value result; napi_status status = napi_coerce_to_number(_env, _value, &result); - NAPI_MAYBE_RETURN_OR_THROW_IF_FAILED( + NAPI_RETURN_OR_THROW_IF_FAILED( _env, status, Napi::Number(_env, result), Napi::Number); } inline MaybeOrValue Value::ToString() const { napi_value result; napi_status status = napi_coerce_to_string(_env, _value, &result); - NAPI_MAYBE_RETURN_OR_THROW_IF_FAILED( + NAPI_RETURN_OR_THROW_IF_FAILED( _env, status, Napi::String(_env, result), Napi::String); } inline MaybeOrValue Value::ToObject() const { napi_value result; napi_status status = napi_coerce_to_object(_env, _value, &result); - NAPI_MAYBE_RETURN_OR_THROW_IF_FAILED( + NAPI_RETURN_OR_THROW_IF_FAILED( _env, status, Napi::Object(_env, result), Napi::Object); } @@ -1252,19 +1252,19 @@ inline MaybeOrValue Object::operator[](uint32_t index) const { inline MaybeOrValue Object::Has(napi_value key) const { bool result; napi_status status = napi_has_property(_env, _value, key, &result); - NAPI_MAYBE_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); } inline MaybeOrValue Object::Has(Value key) const { bool result; napi_status status = napi_has_property(_env, _value, key, &result); - NAPI_MAYBE_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); } inline MaybeOrValue Object::Has(const char* utf8name) const { bool result; napi_status status = napi_has_named_property(_env, _value, utf8name, &result); - NAPI_MAYBE_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); } inline MaybeOrValue Object::Has(const std::string& utf8name) const { @@ -1274,13 +1274,13 @@ inline MaybeOrValue Object::Has(const std::string& utf8name) const { inline MaybeOrValue Object::HasOwnProperty(napi_value key) const { bool result; napi_status status = napi_has_own_property(_env, _value, key, &result); - NAPI_MAYBE_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); } inline MaybeOrValue Object::HasOwnProperty(Value key) const { bool result; napi_status status = napi_has_own_property(_env, _value, key, &result); - NAPI_MAYBE_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); } inline MaybeOrValue Object::HasOwnProperty(const char* utf8name) const { @@ -1298,22 +1298,19 @@ inline MaybeOrValue Object::HasOwnProperty( inline MaybeOrValue Object::Get(napi_value key) const { napi_value result; napi_status status = napi_get_property(_env, _value, key, &result); - NAPI_MAYBE_RETURN_OR_THROW_IF_FAILED( - _env, status, Value(_env, result), Value); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, Value(_env, result), Value); } inline MaybeOrValue Object::Get(Value key) const { napi_value result; napi_status status = napi_get_property(_env, _value, key, &result); - NAPI_MAYBE_RETURN_OR_THROW_IF_FAILED( - _env, status, Value(_env, result), Value); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, Value(_env, result), Value); } inline MaybeOrValue Object::Get(const char* utf8name) const { napi_value result; napi_status status = napi_get_named_property(_env, _value, utf8name, &result); - NAPI_MAYBE_RETURN_OR_THROW_IF_FAILED( - _env, status, Value(_env, result), Value); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, Value(_env, result), Value); } inline MaybeOrValue Object::Get(const std::string& utf8name) const { @@ -1324,14 +1321,14 @@ template inline MaybeOrValue Object::Set(napi_value key, const ValueType& value) { napi_status status = napi_set_property(_env, _value, key, Value::From(_env, value)); - NAPI_MAYBE_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); } template inline MaybeOrValue Object::Set(Value key, const ValueType& value) { napi_status status = napi_set_property(_env, _value, key, Value::From(_env, value)); - NAPI_MAYBE_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); } template @@ -1339,7 +1336,7 @@ 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_MAYBE_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); } template @@ -1351,13 +1348,13 @@ inline MaybeOrValue Object::Set(const std::string& utf8name, inline MaybeOrValue Object::Delete(napi_value key) { bool result; napi_status status = napi_delete_property(_env, _value, key, &result); - NAPI_MAYBE_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); } inline MaybeOrValue Object::Delete(Value key) { bool result; napi_status status = napi_delete_property(_env, _value, key, &result); - NAPI_MAYBE_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); } inline MaybeOrValue Object::Delete(const char* utf8name) { @@ -1371,60 +1368,60 @@ inline MaybeOrValue Object::Delete(const std::string& utf8name) { inline MaybeOrValue Object::Has(uint32_t index) const { bool result; napi_status status = napi_has_element(_env, _value, index, &result); - NAPI_MAYBE_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); } inline MaybeOrValue Object::Get(uint32_t index) const { napi_value value; napi_status status = napi_get_element(_env, _value, index, &value); - NAPI_MAYBE_RETURN_OR_THROW_IF_FAILED(_env, status, Value(_env, value), Value); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, Value(_env, value), Value); } template inline MaybeOrValue Object::Set(uint32_t index, const ValueType& value) { napi_status status = napi_set_element(_env, _value, index, Value::From(_env, value)); - NAPI_MAYBE_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); } inline MaybeOrValue Object::Delete(uint32_t index) { bool result; napi_status status = napi_delete_element(_env, _value, index, &result); - NAPI_MAYBE_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); } -inline MaybeOrValue Object::GetPropertyNames() { +inline MaybeOrValue Object::GetPropertyNames() const { napi_value result; napi_status status = napi_get_property_names(_env, _value, &result); - NAPI_MAYBE_RETURN_OR_THROW_IF_FAILED( - _env, status, Array(_env, result), Array); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, Array(_env, result), Array); } inline MaybeOrValue Object::DefineProperty( const PropertyDescriptor& property) { napi_status status = napi_define_properties(_env, _value, 1, reinterpret_cast(&property)); - NAPI_MAYBE_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); } inline MaybeOrValue Object::DefineProperties( const std::initializer_list& properties) { napi_status status = napi_define_properties(_env, _value, properties.size(), reinterpret_cast(properties.begin())); - NAPI_MAYBE_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); } inline MaybeOrValue Object::DefineProperties( const std::vector& properties) { napi_status status = napi_define_properties(_env, _value, properties.size(), reinterpret_cast(properties.data())); - NAPI_MAYBE_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); } -inline MaybeOrValue Object::InstanceOf(const Function& constructor) { +inline MaybeOrValue Object::InstanceOf( + const Function& constructor) const { bool result; napi_status status = napi_instanceof(_env, _value, constructor, &result); - NAPI_MAYBE_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, result, bool); } template @@ -1466,12 +1463,12 @@ inline void Object::AddFinalizer(Finalizer finalizeCallback, #if NAPI_VERSION >= 8 inline MaybeOrValue Object::Freeze() { napi_status status = napi_object_freeze(_env, _value); - NAPI_MAYBE_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); } inline MaybeOrValue Object::Seal() { napi_status status = napi_object_seal(_env, _value); - NAPI_MAYBE_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); } #endif // NAPI_VERSION >= 8 @@ -2154,7 +2151,7 @@ inline MaybeOrValue Function::Call(napi_value recv, napi_value result; napi_status status = napi_call_function( _env, recv, _value, argc, args, &result); - NAPI_MAYBE_RETURN_OR_THROW_IF_FAILED( + NAPI_RETURN_OR_THROW_IF_FAILED( _env, status, Napi::Value(_env, result), Napi::Value); } @@ -2180,7 +2177,7 @@ inline MaybeOrValue Function::MakeCallback( napi_value result; napi_status status = napi_make_callback( _env, context, recv, _value, argc, args, &result); - NAPI_MAYBE_RETURN_OR_THROW_IF_FAILED( + NAPI_RETURN_OR_THROW_IF_FAILED( _env, status, Napi::Value(_env, result), Napi::Value); } @@ -2199,7 +2196,7 @@ inline MaybeOrValue Function::New(size_t argc, napi_value result; napi_status status = napi_new_instance( _env, _value, argc, args, &result); - NAPI_MAYBE_RETURN_OR_THROW_IF_FAILED( + NAPI_RETURN_OR_THROW_IF_FAILED( _env, status, Napi::Object(_env, result), Napi::Object); } @@ -2488,7 +2485,6 @@ inline const std::string& Error::Message() const NAPI_NOEXCEPT { inline void Error::ThrowAsJavaScriptException() const { HandleScope scope(_env); if (!IsEmpty()) { - // We intentionally don't use `NAPI_THROW_*` macros here to ensure // that there is no possible recursion as `ThrowAsJavaScriptException` // is part of `NAPI_THROW_*` macro definition for noexcept. @@ -2772,94 +2768,146 @@ inline ObjectReference::ObjectReference(const ObjectReference& other) inline MaybeOrValue ObjectReference::Get( const char* utf8name) const { - return Value().Get(utf8name); + EscapableHandleScope scope(_env); + 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 MaybeOrValue ObjectReference::Get( const std::string& utf8name) const { - return Value().Get(utf8name); + EscapableHandleScope scope(_env); + 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 MaybeOrValue ObjectReference::Set(const char* utf8name, napi_value value) { + HandleScope scope(_env); return Value().Set(utf8name, value); } inline MaybeOrValue ObjectReference::Set(const char* utf8name, Napi::Value value) { + HandleScope scope(_env); return Value().Set(utf8name, value); } inline MaybeOrValue ObjectReference::Set(const char* utf8name, const char* utf8value) { + HandleScope scope(_env); return Value().Set(utf8name, utf8value); } inline MaybeOrValue ObjectReference::Set(const char* utf8name, bool boolValue) { + HandleScope scope(_env); return Value().Set(utf8name, boolValue); } inline MaybeOrValue ObjectReference::Set(const char* utf8name, double numberValue) { + HandleScope scope(_env); return Value().Set(utf8name, numberValue); } inline MaybeOrValue ObjectReference::Set(const std::string& utf8name, napi_value value) { + HandleScope scope(_env); return Value().Set(utf8name, value); } inline MaybeOrValue ObjectReference::Set(const std::string& utf8name, Napi::Value value) { + HandleScope scope(_env); return Value().Set(utf8name, value); } inline MaybeOrValue ObjectReference::Set(const std::string& utf8name, std::string& utf8value) { + HandleScope scope(_env); return Value().Set(utf8name, utf8value); } inline MaybeOrValue ObjectReference::Set(const std::string& utf8name, bool boolValue) { + HandleScope scope(_env); return Value().Set(utf8name, boolValue); } inline MaybeOrValue ObjectReference::Set(const std::string& utf8name, double numberValue) { + HandleScope scope(_env); return Value().Set(utf8name, numberValue); } inline MaybeOrValue ObjectReference::Get(uint32_t index) const { - return Value().Get(index); + EscapableHandleScope scope(_env); + 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 MaybeOrValue ObjectReference::Set(uint32_t index, napi_value value) { + HandleScope scope(_env); return Value().Set(index, value); } inline MaybeOrValue ObjectReference::Set(uint32_t index, Napi::Value value) { + HandleScope scope(_env); return Value().Set(index, value); } inline MaybeOrValue ObjectReference::Set(uint32_t index, const char* utf8value) { + HandleScope scope(_env); return Value().Set(index, utf8value); } inline MaybeOrValue ObjectReference::Set(uint32_t index, const std::string& utf8value) { + HandleScope scope(_env); return Value().Set(index, utf8value); } inline MaybeOrValue ObjectReference::Set(uint32_t index, bool boolValue) { + HandleScope scope(_env); return Value().Set(index, boolValue); } inline MaybeOrValue ObjectReference::Set(uint32_t index, double numberValue) { + HandleScope scope(_env); return Value().Set(index, numberValue); } diff --git a/napi.h b/napi.h index c0ef9a24d..7ca102105 100644 --- a/napi.h +++ b/napi.h @@ -102,14 +102,14 @@ static_assert(sizeof(char16_t) == sizeof(wchar_t), "Size mismatch between char16 #define NAPI_MAYBE_THROW_IF_FAILED(env, status, type) \ NAPI_THROW_IF_FAILED(env, status, Napi::Nothing()) -#define NAPI_MAYBE_RETURN_OR_THROW_IF_FAILED(env, status, result, type) \ +#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_MAYBE_RETURN_OR_THROW_IF_FAILED(env, status, result, type) \ +#define NAPI_RETURN_OR_THROW_IF_FAILED(env, status, result, type) \ NAPI_MAYBE_THROW_IF_FAILED(env, status, type); \ return result; #endif @@ -831,7 +831,7 @@ namespace Napi { /// 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(); ///< Get all property names + MaybeOrValue GetPropertyNames() const; ///< Get all property names /// Defines a property on the object. /// @@ -873,7 +873,7 @@ namespace Napi { /// https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-getprototypeof MaybeOrValue InstanceOf( const Function& constructor ///< Constructor function - ); + ) const; template inline void AddFinalizer(Finalizer finalizeCallback, T* data); diff --git a/test/README.md b/test/README.md index 64f09180c..6f29fb215 100644 --- a/test/README.md +++ b/test/README.md @@ -7,16 +7,16 @@ build flags defined in `napi.h`: 2. c++ exceptions disabled, 3. c++ exceptions disabled, and `NODE_ADDON_API_ENABLE_MAYBE` is defined. -`Napi` functions that calling into JavaScript can have different declared -`NODE_ADDON_API_ENABLE_MAYBE` is defined and c++ exceptions disabled -return types to reflect build flavor settings. For example, -`Napi::Object::Set` returns `bool`, and `Napi::Maybe` when -`NODE_ADDON_API_ENABLE_MAYBE` is defined. In source code, return type variants -are defined as `Napi::MaybeOrValue<>` to prevent from duplicating most of the -code base. +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 from duplicating most part of the code base. -To properly test these build flavors, we should take care of the return value -of `Napi` functions that may call into JavaScript. For example: +To properly test these build flavors, all values returned by a function defined +with `Napi::MaybeOrValue<>` return types in node-addon-api test suite, should +use one of the following test helpers to handle possible JavaScript exceptions. ```cpp #include "napi.h" @@ -24,13 +24,17 @@ of `Napi` functions that may call into JavaScript. For example: using namespace Napi; -Value fn(const CallbackInfo& info) { +void fn(const CallbackInfo& info) { Object obj = info[0].As(); - Value value = MaybeUnwrap(obj->Get("foobar")); // <- `obj->Get` may throws + Value value = MaybeUnwrap(obj->Get("foobar")); // <- `obj->Get` is calling + // into JavaScript and may throw JavaScript Exceptions. Here we just assert + // getting the parameter must not fail for convenience. + + // ... do works with the value. } ``` -There are there test helper function to conveniently convert `Napi::MaybeOrValue<>` +There are three test helper functions to conveniently convert `Napi::MaybeOrValue<>` type to raw types. ## MaybeUnwrap @@ -49,7 +53,8 @@ Example: ```cpp Object obj = info[0].As(); -Value value = MaybeUnwrap(obj->Get("foobar")); // we are sure the parameters should not throw +Value value = MaybeUnwrap(obj->Get("foobar")); // we are sure the parameters +// should not throw ``` ## MaybeUnwrapOr From baf6ab8e57b16a921c2b134df7077e8e63ab21aa Mon Sep 17 00:00:00 2001 From: legendecas Date: Fri, 18 Jun 2021 22:12:17 +0800 Subject: [PATCH 08/10] fixup! remove unnecessary MaybeUnwrapOr --- test/README.md | 16 ------------- test/basic_types/value.cc | 8 +++---- test/common/test_helper.h | 2 +- test/function.cc | 24 +++++++++---------- test/functionreference.cc | 4 ++-- .../global_object_delete_property.cc | 8 +++---- .../global_object_get_property.cc | 8 +++---- .../global_object_has_own_property.cc | 8 ++++--- test/object/delete_property.cc | 16 +++++++------ test/object/get_property.cc | 10 ++++---- test/object/has_own_property.cc | 12 ++++++---- test/object/has_property.cc | 14 ++++++----- test/object/object.cc | 4 ++-- test/object/object_freeze_seal.cc | 4 ++-- test/object/set_property.cc | 12 ++++++---- test/objectreference.cc | 18 +++++++------- test/objectwrap.cc | 4 ++-- test/run_script.cc | 8 +++---- .../threadsafe_function_existing_tsfn.cc | 17 +++++-------- ...typed_threadsafe_function_existing_tsfn.cc | 17 +++++-------- 20 files changed, 99 insertions(+), 115 deletions(-) diff --git a/test/README.md b/test/README.md index 6f29fb215..9f3ad8257 100644 --- a/test/README.md +++ b/test/README.md @@ -18,22 +18,6 @@ To properly test these build flavors, all values returned by a function defined with `Napi::MaybeOrValue<>` return types in node-addon-api test suite, should use one of the following test helpers to handle possible JavaScript exceptions. -```cpp -#include "napi.h" -#include "test_helper.h" - -using namespace Napi; - -void fn(const CallbackInfo& info) { - Object obj = info[0].As(); - Value value = MaybeUnwrap(obj->Get("foobar")); // <- `obj->Get` is calling - // into JavaScript and may throw JavaScript Exceptions. Here we just assert - // getting the parameter must not fail for convenience. - - // ... do works with the value. -} -``` - There are three test helper functions to conveniently convert `Napi::MaybeOrValue<>` type to raw types. diff --git a/test/basic_types/value.cc b/test/basic_types/value.cc index 563911c5f..59de71202 100644 --- a/test/basic_types/value.cc +++ b/test/basic_types/value.cc @@ -76,19 +76,19 @@ static Value IsExternal(const CallbackInfo& info) { } static Value ToBoolean(const CallbackInfo& info) { - return MaybeUnwrapOr(info[0].ToBoolean()); + return MaybeUnwrap(info[0].ToBoolean()); } static Value ToNumber(const CallbackInfo& info) { - return MaybeUnwrapOr(info[0].ToNumber()); + return MaybeUnwrap(info[0].ToNumber()); } static Value ToString(const CallbackInfo& info) { - return MaybeUnwrapOr(info[0].ToString()); + return MaybeUnwrap(info[0].ToString()); } static Value ToObject(const CallbackInfo& info) { - return MaybeUnwrapOr(info[0].ToObject()); + return MaybeUnwrap(info[0].ToObject()); } Object InitBasicTypesValue(Env env) { diff --git a/test/common/test_helper.h b/test/common/test_helper.h index adb8b8f6f..f25b54651 100644 --- a/test/common/test_helper.h +++ b/test/common/test_helper.h @@ -32,7 +32,7 @@ inline T MaybeUnwrap(MaybeOrValue maybe) { * Do nothing when NODE_ADDON_API_ENABLE_MAYBE is not defined. */ template -inline T MaybeUnwrapOr(MaybeOrValue maybe, const T& default_value = T()) { +inline T MaybeUnwrapOr(MaybeOrValue maybe, const T& default_value) { #if defined(NODE_ADDON_API_ENABLE_MAYBE) return maybe.UnwrapOr(default_value); #else diff --git a/test/function.cc b/test/function.cc index 66d42a287..0fab27290 100644 --- a/test/function.cc +++ b/test/function.cc @@ -54,7 +54,7 @@ Value ValueCallbackWithData(const CallbackInfo& info) { Value CallWithArgs(const CallbackInfo& info) { Function func = info[0].As(); - return MaybeUnwrapOr( + return MaybeUnwrap( func.Call(std::initializer_list{info[1], info[2], info[3]})); } @@ -65,7 +65,7 @@ Value CallWithVector(const CallbackInfo& info) { args.push_back(info[1]); args.push_back(info[2]); args.push_back(info[3]); - return MaybeUnwrapOr(func.Call(args)); + return MaybeUnwrap(func.Call(args)); } Value CallWithCStyleArray(const CallbackInfo& info) { @@ -75,7 +75,7 @@ Value CallWithCStyleArray(const CallbackInfo& info) { args.push_back(info[1]); args.push_back(info[2]); args.push_back(info[3]); - return MaybeUnwrapOr(func.Call(args.size(), args.data())); + return MaybeUnwrap(func.Call(args.size(), args.data())); } Value CallWithReceiverAndCStyleArray(const CallbackInfo& info) { @@ -86,13 +86,13 @@ Value CallWithReceiverAndCStyleArray(const CallbackInfo& info) { args.push_back(info[2]); args.push_back(info[3]); args.push_back(info[4]); - return MaybeUnwrapOr(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 MaybeUnwrapOr(func.Call( + return MaybeUnwrap(func.Call( receiver, std::initializer_list{info[2], info[3], info[4]})); } @@ -104,18 +104,18 @@ Value CallWithReceiverAndVector(const CallbackInfo& info) { args.push_back(info[2]); args.push_back(info[3]); args.push_back(info[4]); - return MaybeUnwrapOr(func.Call(receiver, args)); + return MaybeUnwrap(func.Call(receiver, args)); } Value CallWithInvalidReceiver(const CallbackInfo& info) { Function func = info[0].As(); - return MaybeUnwrapOr( - 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 MaybeUnwrapOr( + return MaybeUnwrap( func.New(std::initializer_list{info[1], info[2], info[3]})); } @@ -126,7 +126,7 @@ Value CallConstructorWithVector(const CallbackInfo& info) { args.push_back(info[1]); args.push_back(info[2]); args.push_back(info[3]); - return MaybeUnwrapOr(func.New(args)); + return MaybeUnwrap(func.New(args)); } Value CallConstructorWithCStyleArray(const CallbackInfo& info) { @@ -136,7 +136,7 @@ Value CallConstructorWithCStyleArray(const CallbackInfo& info) { args.push_back(info[1]); args.push_back(info[2]); args.push_back(info[3]); - return MaybeUnwrapOr(func.New(args.size(), args.data())); + return MaybeUnwrap(func.New(args.size(), args.data())); } void IsConstructCall(const CallbackInfo& info) { @@ -195,7 +195,7 @@ void MakeCallbackWithInvalidReceiver(const CallbackInfo& info) { Value CallWithFunctionOperator(const CallbackInfo& info) { Function func = info[0].As(); - return MaybeUnwrapOr(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 392697a54..aaa899e11 100644 --- a/test/functionreference.cc +++ b/test/functionreference.cc @@ -9,7 +9,7 @@ Value Call(const CallbackInfo& info) { FunctionReference ref; ref.Reset(info[0].As()); - return MaybeUnwrapOr(ref.Call({})); + return MaybeUnwrapOr(ref.Call({}), Value()); } Value Construct(const CallbackInfo& info) { @@ -17,7 +17,7 @@ Value Construct(const CallbackInfo& info) { FunctionReference ref; ref.Reset(info[0].As()); - return MaybeUnwrapOr(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 7f24030f3..295ed3f36 100644 --- a/test/globalObject/global_object_delete_property.cc +++ b/test/globalObject/global_object_delete_property.cc @@ -7,25 +7,25 @@ Value DeletePropertyWithCStyleStringAsKey(const CallbackInfo& info) { Object globalObject = info.Env().Global(); String key = info[0].As(); return Boolean::New( - info.Env(), MaybeUnwrapOr(globalObject.Delete(key.Utf8Value().c_str()))); + 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(), - MaybeUnwrapOr(globalObject.Delete(key.Utf8Value()))); + 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(), - MaybeUnwrapOr(globalObject.Delete(key.Uint32Value()))); + 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(), MaybeUnwrapOr(globalObject.Delete(key))); + 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 5abf9777c..dd112043c 100644 --- a/test/globalObject/global_object_get_property.cc +++ b/test/globalObject/global_object_get_property.cc @@ -6,25 +6,25 @@ using namespace Napi; Value GetPropertyWithNapiValueAsKey(const CallbackInfo& info) { Object globalObject = info.Env().Global(); Name key = info[0].As(); - return MaybeUnwrapOr(globalObject.Get(key)); + return MaybeUnwrap(globalObject.Get(key)); } Value GetPropertyWithInt32AsKey(const CallbackInfo& info) { Object globalObject = info.Env().Global(); Number key = info[0].As(); - return MaybeUnwrapOr(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 MaybeUnwrapOr(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 MaybeUnwrapOr(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 0ca95f526..89c299913 100644 --- a/test/globalObject/global_object_has_own_property.cc +++ b/test/globalObject/global_object_has_own_property.cc @@ -8,19 +8,21 @@ Value HasPropertyWithCStyleStringAsKey(const CallbackInfo& info) { String key = info[0].As(); return Boolean::New( info.Env(), - MaybeUnwrapOr(globalObject.HasOwnProperty(key.Utf8Value().c_str()))); + 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(), MaybeUnwrapOr(globalObject.HasOwnProperty(key.Utf8Value()))); + 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(), - MaybeUnwrapOr(globalObject.HasOwnProperty(key))); + MaybeUnwrap(globalObject.HasOwnProperty(key))); } diff --git a/test/object/delete_property.cc b/test/object/delete_property.cc index 69f2c03d6..ca69e5387 100644 --- a/test/object/delete_property.cc +++ b/test/object/delete_property.cc @@ -6,31 +6,33 @@ using namespace Napi; Value DeletePropertyWithUint32(const CallbackInfo& info) { Object obj = info[0].As(); Number key = info[1].As(); - return Boolean::New(info.Env(), MaybeUnwrapOr(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(), - MaybeUnwrapOr(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(), MaybeUnwrapOr(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(), - MaybeUnwrapOr(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(), MaybeUnwrapOr(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 c2e832013..523f99199 100644 --- a/test/object/get_property.cc +++ b/test/object/get_property.cc @@ -6,29 +6,29 @@ using namespace Napi; Value GetPropertyWithNapiValue(const CallbackInfo& info) { Object obj = info[0].As(); Name key = info[1].As(); - return MaybeUnwrapOr(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 MaybeUnwrapOr(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 MaybeUnwrapOr(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 MaybeUnwrapOr(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 MaybeUnwrapOr(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 748df2d01..d7fbde98b 100644 --- a/test/object/has_own_property.cc +++ b/test/object/has_own_property.cc @@ -8,25 +8,27 @@ Value HasOwnPropertyWithNapiValue(const CallbackInfo& info) { Name key = info[1].As(); return Boolean::New( info.Env(), - MaybeUnwrapOr(obj.HasOwnProperty(static_cast(key)))); + 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(), MaybeUnwrapOr(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(), MaybeUnwrapOr(obj.HasOwnProperty(jsKey.Utf8Value().c_str()))); + 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(), - MaybeUnwrapOr(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 e26bc240c..fa410833f 100644 --- a/test/object/has_property.cc +++ b/test/object/has_property.cc @@ -6,31 +6,33 @@ using namespace Napi; Value HasPropertyWithNapiValue(const CallbackInfo& info) { Object obj = info[0].As(); Name key = info[1].As(); - return Boolean::New(info.Env(), - MaybeUnwrapOr(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(), MaybeUnwrapOr(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(), - MaybeUnwrapOr(obj.Has(jsKey.Utf8Value().c_str()))); + 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(), MaybeUnwrapOr(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(), MaybeUnwrapOr(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 6ba12d190..a73b8e354 100644 --- a/test/object/object.cc +++ b/test/object/object.cc @@ -96,7 +96,7 @@ Value ConstructorFromObject(const CallbackInfo& info) { Array GetPropertyNames(const CallbackInfo& info) { Object obj = info[0].As(); - Array arr = MaybeUnwrapOr(obj.GetPropertyNames()); + Array arr = MaybeUnwrap(obj.GetPropertyNames()); return arr; } @@ -256,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(), MaybeUnwrapOr(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 b461c3ccf..40aaeb467 100644 --- a/test/object/object_freeze_seal.cc +++ b/test/object/object_freeze_seal.cc @@ -7,12 +7,12 @@ using namespace Napi; Value Freeze(const CallbackInfo& info) { Object obj = info[0].As(); - return Boolean::New(info.Env(), MaybeUnwrapOr(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(), MaybeUnwrapOr(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 f7aa65807..5f10b4f09 100644 --- a/test/object/set_property.cc +++ b/test/object/set_property.cc @@ -8,22 +8,24 @@ Value SetPropertyWithNapiValue(const CallbackInfo& info) { Name key = info[1].As(); Value value = info[2]; return Boolean::New( - info.Env(), MaybeUnwrapOr(obj.Set(static_cast(key), value))); + 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(), MaybeUnwrapOr(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(), - MaybeUnwrapOr(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) { @@ -31,5 +33,5 @@ Value SetPropertyWithCppStyleString(const CallbackInfo& info) { String jsKey = info[1].As(); Value value = info[2]; return Boolean::New(info.Env(), - MaybeUnwrapOr(obj.Set(jsKey.Utf8Value(), value))); + MaybeUnwrapOr(obj.Set(jsKey.Utf8Value(), value), false)); } diff --git a/test/objectreference.cc b/test/objectreference.cc index 46fb38159..34f952088 100644 --- a/test/objectreference.cc +++ b/test/objectreference.cc @@ -98,22 +98,22 @@ Value GetFromGetter(const CallbackInfo& info) { return String::New(env, "No Referenced Value"); } else { if (info[1].IsString()) { - return MaybeUnwrapOr(weak.Get(info[1].As().Utf8Value())); + return MaybeUnwrap(weak.Get(info[1].As().Utf8Value())); } else if (info[1].IsNumber()) { - return MaybeUnwrapOr(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 MaybeUnwrapOr(persistent.Get(info[1].As().Utf8Value())); + return MaybeUnwrap(persistent.Get(info[1].As().Utf8Value())); } else if (info[1].IsNumber()) { - return MaybeUnwrapOr(persistent.Get(info[1].As().Uint32Value())); + return MaybeUnwrap(persistent.Get(info[1].As().Uint32Value())); } } else { if (info[0].IsString()) { - return MaybeUnwrapOr(reference.Get(info[0].As().Utf8Value())); + return MaybeUnwrap(reference.Get(info[0].As().Utf8Value())); } else if (info[0].IsNumber()) { - return MaybeUnwrapOr(reference.Get(info[0].As().Uint32Value())); + return MaybeUnwrap(reference.Get(info[0].As().Uint32Value())); } } @@ -148,12 +148,12 @@ Value GetCastedFromGetter(const CallbackInfo& info) { if (casted_weak.IsEmpty()) { return String::New(env, "No Referenced Value"); } else { - return MaybeUnwrapOr(casted_weak.Get(info[1].As())); + return MaybeUnwrap(casted_weak.Get(info[1].As())); } } else if (info[0].As() == String::New(env, "persistent")) { - return MaybeUnwrapOr(casted_persistent.Get(info[1].As())); + return MaybeUnwrap(casted_persistent.Get(info[1].As())); } else { - return MaybeUnwrapOr(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 2a8300a75..65727a3df 100644 --- a/test/objectwrap.cc +++ b/test/objectwrap.cc @@ -4,7 +4,7 @@ Napi::ObjectReference testStaticContextRef; Napi::Value StaticGetter(const Napi::CallbackInfo& /*info*/) { - return MaybeUnwrapOr(testStaticContextRef.Value().Get("value")); + return MaybeUnwrap(testStaticContextRef.Value().Get("value")); } void StaticSetter(const Napi::CallbackInfo& /*info*/, const Napi::Value& value) { @@ -81,7 +81,7 @@ 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 MaybeUnwrapOr( + return MaybeUnwrap( MaybeUnwrap(array.Get(MaybeUnwrap( Napi::Symbol::WellKnown(info.Env(), "iterator")))) .As() diff --git a/test/run_script.cc b/test/run_script.cc index 9ec2d7cb2..507164cad 100644 --- a/test/run_script.cc +++ b/test/run_script.cc @@ -7,18 +7,18 @@ namespace { Value RunPlainString(const CallbackInfo& info) { Env env = info.Env(); - return MaybeUnwrapOr(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 MaybeUnwrapOr(env.RunScript(str)); + return MaybeUnwrap(env.RunScript(str)); } Value RunJsString(const CallbackInfo& info) { Env env = info.Env(); - return MaybeUnwrapOr(env.RunScript(info[0].As())); + return MaybeUnwrapOr(env.RunScript(info[0].As()), Value()); } Value RunWithContext(const CallbackInfo& info) { @@ -39,7 +39,7 @@ Value RunWithContext(const CallbackInfo& info) { Value key = MaybeUnwrap(keys.Get(i)); args.push_back(MaybeUnwrap(info[1].As().Get(key))); } - return MaybeUnwrapOr(fn.Call(args)); + return MaybeUnwrap(fn.Call(args)); } } // end anonymous namespace diff --git a/test/threadsafe_function/threadsafe_function_existing_tsfn.cc b/test/threadsafe_function/threadsafe_function_existing_tsfn.cc index 126df5dde..9307b22f1 100644 --- a/test/threadsafe_function/threadsafe_function_existing_tsfn.cc +++ b/test/threadsafe_function/threadsafe_function_existing_tsfn.cc @@ -60,18 +60,13 @@ static Value TestCall(const CallbackInfo &info) { bool hasData = false; if (info.Length() > 0) { Object opts = info[0].As(); - bool hasProperty = false; - if (MaybeUnwrapTo(opts.Has("blocking"), &hasProperty)) { - isBlocking = hasProperty && - MaybeUnwrap(MaybeUnwrap(opts.Get("blocking")).ToBoolean()); - } else { - env.GetAndClearPendingException(); + bool hasProperty = MaybeUnwrap(opts.Has("blocking")); + if (hasProperty) { + isBlocking = MaybeUnwrap(MaybeUnwrap(opts.Get("blocking")).ToBoolean()); } - if (MaybeUnwrapTo(opts.Has("data"), &hasProperty)) { - hasData = - hasProperty && MaybeUnwrap(MaybeUnwrap(opts.Get("data")).ToBoolean()); - } else { - env.GetAndClearPendingException(); + hasProperty = MaybeUnwrap(opts.Has("data")); + if (hasProperty) { + hasData = MaybeUnwrap(MaybeUnwrap(opts.Get("data")).ToBoolean()); } } 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 f2c1a1224..daa273fcb 100644 --- a/test/typed_threadsafe_function/typed_threadsafe_function_existing_tsfn.cc +++ b/test/typed_threadsafe_function/typed_threadsafe_function_existing_tsfn.cc @@ -65,18 +65,13 @@ static Value TestCall(const CallbackInfo& info) { bool hasData = false; if (info.Length() > 0) { Object opts = info[0].As(); - bool hasProperty = false; - if (MaybeUnwrapTo(opts.Has("blocking"), &hasProperty)) { - isBlocking = hasProperty && - MaybeUnwrap(MaybeUnwrap(opts.Get("blocking")).ToBoolean()); - } else { - env.GetAndClearPendingException(); + bool hasProperty = MaybeUnwrap(opts.Has("blocking")); + if (hasProperty) { + isBlocking = MaybeUnwrap(MaybeUnwrap(opts.Get("blocking")).ToBoolean()); } - if (MaybeUnwrapTo(opts.Has("data"), &hasProperty)) { - hasData = - hasProperty && MaybeUnwrap(MaybeUnwrap(opts.Get("data")).ToBoolean()); - } else { - env.GetAndClearPendingException(); + hasProperty = MaybeUnwrap(opts.Has("data")); + if (hasProperty) { + hasData = MaybeUnwrap(MaybeUnwrap(opts.Get("data")).ToBoolean()); } } From 43e14e544a7ec32eeedb33127d18469e8fb40d12 Mon Sep 17 00:00:00 2001 From: legendecas Date: Thu, 15 Jul 2021 00:38:13 +0800 Subject: [PATCH 09/10] fixup! apply review suggestions --- doc/error_handling.md | 9 +++++---- doc/maybe.md | 8 +++++--- doc/setup.md | 2 +- test/README.md | 22 +++++++++++----------- test/common/test_helper.h | 2 +- 5 files changed, 23 insertions(+), 20 deletions(-) diff --git a/doc/error_handling.md b/doc/error_handling.md index 639cbd64f..57de85c9e 100644 --- a/doc/error_handling.md +++ b/doc/error_handling.md @@ -103,16 +103,17 @@ exception. 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 function do not throw a C++ exceptions. Instead, these node-api +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 calling into another node-api (for more info see: [Env](env.md)). +attempting to call another node-api (for more info see: [Env](env.md)). -The conversion from `Maybe` boxed values to actual return value is enforced by -compilers so that the exceptions must be properly handled before continuing. +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 diff --git a/doc/maybe.md b/doc/maybe.md index c03387f60..dc71c0750 100644 --- a/doc/maybe.md +++ b/doc/maybe.md @@ -18,7 +18,8 @@ template bool Napi::Maybe::IsNothing() const; ``` -Returns if the `Maybe` is `Nothing` and does not contain a value. +Returns `true` if the `Maybe` is `Nothing` and does not contain a value, and +`false` otherwise. ### IsJust @@ -27,7 +28,8 @@ template bool Napi::Maybe::IsJust() const; ``` -Returns if the `Maybe` is `Just` and contains a value. +Returns `true` if the `Maybe` is `Just` and contains a value, and `false` +otherwise. ### Check @@ -65,7 +67,7 @@ value if this Maybe is nothing (empty). ```cpp template -bool Napi::Maybe::UnwrapTo() const; +bool Napi::Maybe::UnwrapTo(T* result) const; ``` Converts this Maybe to a value of type `T` in the `out`. If this Maybe is diff --git a/doc/setup.md b/doc/setup.md index 591b99bd5..5db3452ad 100644 --- a/doc/setup.md +++ b/doc/setup.md @@ -56,7 +56,7 @@ To use **Node-API** in a native module: ``` If you decide to use node-addon-api without C++ exceptions enabled, please - consider enabling node-addon-api safe API type guards to ensure proper + consider enabling node-addon-api safe API type guards to ensure the proper exception handling pattern: ```gyp diff --git a/test/README.md b/test/README.md index 9f3ad8257..7ea20d765 100644 --- a/test/README.md +++ b/test/README.md @@ -1,25 +1,25 @@ # Writing Tests -There are multiple flavors of node-addon-api test build that covers different +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` is defined. +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 from duplicating most part of the code base. +`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 -with `Napi::MaybeOrValue<>` return types in node-addon-api test suite, should -use one of the following test helpers to handle possible JavaScript exceptions. +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<>` -type to raw types. +There are three test helper functions to conveniently convert +`Napi::MaybeOrValue<>` values to raw values. ## MaybeUnwrap @@ -37,8 +37,8 @@ Example: ```cpp Object obj = info[0].As(); -Value value = MaybeUnwrap(obj->Get("foobar")); // we are sure the parameters -// should not throw +// we are sure the parameters should not throw +Value value = MaybeUnwrap(obj->Get("foobar")); ``` ## MaybeUnwrapOr @@ -68,7 +68,7 @@ Value CallWithArgs(const CallbackInfo& info) { ```cpp template -T MaybeUnwrapTo(MaybeOrValue maybe, T* out); +bool MaybeUnwrapTo(MaybeOrValue maybe, T* out); ``` Converts `MaybeOrValue` to `T` by getting the value that wrapped by the @@ -81,7 +81,7 @@ Example: ```cpp Object opts = info[0].As(); bool hasProperty = false; -// The check may throwing, but we are going to suppress that. +// 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()); diff --git a/test/common/test_helper.h b/test/common/test_helper.h index f25b54651..1b321a19e 100644 --- a/test/common/test_helper.h +++ b/test/common/test_helper.h @@ -49,7 +49,7 @@ inline T MaybeUnwrapOr(MaybeOrValue maybe, const T& default_value) { * Copying the value to out when NODE_ADDON_API_ENABLE_MAYBE is not defined. */ template -inline T MaybeUnwrapTo(MaybeOrValue maybe, T* out) { +inline bool MaybeUnwrapTo(MaybeOrValue maybe, T* out) { #if defined(NODE_ADDON_API_ENABLE_MAYBE) return maybe.UnwrapTo(out); #else From 68facc75b323b1a39759586cf80e18901597fa87 Mon Sep 17 00:00:00 2001 From: legendecas Date: Thu, 15 Jul 2021 00:59:15 +0800 Subject: [PATCH 10/10] fixup! update Symbol::For --- napi-inl.h | 32 ++++++++++++++++++++++++-------- napi.h | 9 +++++---- test/symbol.cc | 16 +++++++++------- 3 files changed, 38 insertions(+), 19 deletions(-) diff --git a/napi-inl.h b/napi-inl.h index b198df514..25c92a7d9 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -1080,25 +1080,41 @@ inline MaybeOrValue Symbol::WellKnown(napi_env env, #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() { diff --git a/napi.h b/napi.h index 1c3e393e2..8f4d39a6e 100644 --- a/napi.h +++ b/napi.h @@ -630,16 +630,17 @@ namespace Napi { 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, 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) {