From 09c7b71274cf3cf6d18a8c9ea15e5ebfab653645 Mon Sep 17 00:00:00 2001 From: Jason Ginchereau Date: Fri, 9 Jun 2017 11:49:48 -0700 Subject: [PATCH] Conditional support for C++ exceptions (#52) Enable using the N-API C++ wrapper classes with or without C++ exceptions. See the updated `Napi::Error` class documentation for an overview of the developer experience. - Add a `NAPI_CPP_EXCEPTIONS` preprocessor symbol that is defined when C++ exceptions are enabled. - Add `Env::GetAndClearPendingException()` method. - Add `Value::IsEmpty()` method. - Update documentation on Error class to provide parallel explanation and examples for error-handling without C++ exceptions. - Update README to mention optional C++ exception support. - Define a `NAPI_THROW_IF_FAILED()` macro that throws either a C++ or JS exception depending on whether `NAPI_CPP_EXCEPTIONS` is defined. - Define a `details::WrapCallback()` helper function that catches C++ exceptions thrown from callbacks, only if `NAPI_CPP_EXCEPTIONS` is defined. - Update implementation of all methods to use `NAPI_THROW_IF_FAILED()` and `details::WrapCallback()` as appropriate. - Fix a bug in `Error::New()` when there was a pending exception but some different error status was reported by the last error info. - Update `test/binding.gyp` to build two separate modules, with and without C++ exceptions enabled. - Update test JS code to run the same tests against both modules. - Update test C++ code to throw JS exceptions (to be compatible with both modes). - Add some additional test cases to verify expected exceptions are observed from JS regardless of whether C++ exceptions are enabled or not. - Change CI config to ignore failures on nightly builds. --- .editorconfig | 8 + .travis.yml | 6 + README.md | 13 +- appveyor.yml | 6 + napi-inl.h | 382 +++++++++++++++++++++++++------------------- napi.h | 98 ++++++++++-- package.json | 2 +- test/arraybuffer.cc | 30 ++-- test/arraybuffer.js | 92 ++++++----- test/asyncworker.js | 20 +-- test/binding.gyp | 33 +++- test/buffer.cc | 39 +++-- test/buffer.js | 100 ++++++------ test/error.cc | 97 +++++++++-- test/error.js | 92 ++++++----- test/external.cc | 6 +- test/external.js | 70 ++++---- test/function.cc | 6 + test/function.js | 98 ++++++------ test/name.cc | 9 +- test/name.js | 96 +++++------ test/object.cc | 16 ++ test/object.js | 142 +++++++++------- test/typedarray.cc | 14 +- test/typedarray.js | 54 ++++--- 25 files changed, 946 insertions(+), 583 deletions(-) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..5e9b7bf3d --- /dev/null +++ b/.editorconfig @@ -0,0 +1,8 @@ +root = true + +[*] +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true +indent_style = space +indent_size = 2 diff --git a/.travis.yml b/.travis.yml index 6c32e470d..c37486fff 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,6 +19,9 @@ env: - NODEJS_VERSION=chakracore-nightly matrix: fast_finish: true + allow_failures: + - env: NODEJS_VERSION=nightly + - env: NODEJS_VERSION=chakracore-nightly sudo: false cache: directories: @@ -50,6 +53,9 @@ before_install: install: - npm install $NPMOPT script: + # Travis CI sets NVM_NODEJS_ORG_MIRROR, but it makes node-gyp fail to download headers for nightly builds. + - unset NVM_NODEJS_ORG_MIRROR + - npm test $NPMOPT after_success: - cpp-coveralls --gcov-options '\-lp' --build-root test/build --exclude test diff --git a/README.md b/README.md index 909e62adf..539db528a 100644 --- a/README.md +++ b/README.md @@ -26,9 +26,12 @@ To use N-API in a native module: 'dependencies': [" +inline napi_value WrapCallback(Callable callback) { +#ifdef NAPI_CPP_EXCEPTIONS + try { + return callback(); + } catch (const Error& e) { + e.ThrowAsJavaScriptException(); + return nullptr; } - -// Helpers to handle functions exposed from C++. -namespace details { +#else // NAPI_CPP_EXCEPTIONS + // When C++ exceptions are disabled, errors are immediately thrown as JS + // exceptions, so there is no need to catch and rethrow them here. + return callback(); +#endif // NAPI_CPP_EXCEPTIONS +} template struct CallbackData { static inline napi_value Wrapper(napi_env env, napi_callback_info info) { - try { + return details::WrapCallback([&] { CallbackInfo callbackInfo(env, info); CallbackData* callbackData = static_cast(callbackInfo.Data()); callbackInfo.SetData(callbackData->data); return callbackData->callback(callbackInfo); - } - NAPI_RETHROW_JS_ERROR(env) + }); } Callable callback; @@ -47,15 +81,14 @@ template struct CallbackData { static inline napi_value Wrapper(napi_env env, napi_callback_info info) { - try { + return details::WrapCallback([&] { CallbackInfo callbackInfo(env, info); CallbackData* callbackData = static_cast(callbackInfo.Data()); callbackInfo.SetData(callbackData->data); callbackData->callback(callbackInfo); return nullptr; - } - NAPI_RETHROW_JS_ERROR(env) + }); } Callable callback; @@ -86,25 +119,23 @@ template struct AccessorCallbackData { static inline napi_value GetterWrapper(napi_env env, napi_callback_info info) { - try { + return details::WrapCallback([&] { CallbackInfo callbackInfo(env, info); AccessorCallbackData* callbackData = static_cast(callbackInfo.Data()); return callbackData->getterCallback(callbackInfo); - } - NAPI_RETHROW_JS_ERROR(env) + }); } static inline napi_value SetterWrapper(napi_env env, napi_callback_info info) { - try { + return details::WrapCallback([&] { CallbackInfo callbackInfo(env, info); AccessorCallbackData* callbackData = static_cast(callbackInfo.Data()); callbackData->setterCallback(callbackInfo); return nullptr; - } - NAPI_RETHROW_JS_ERROR(env) + }); } Getter getterCallback; @@ -133,14 +164,12 @@ inline void RegisterModule(napi_env env, napi_value exports, napi_value module, ModuleRegisterCallback registerCallback) { - try { - registerCallback(Napi::Env(env), - Napi::Object(env, exports), - Napi::Object(env, module)); - } - catch (const Error& e) { - e.ThrowAsJavaScriptException(); - } + details::WrapCallback([&] { + registerCallback(Napi::Env(env), + Napi::Object(env, exports), + Napi::Object(env, module)); + return nullptr; + }); } //////////////////////////////////////////////////////////////////////////////// @@ -157,21 +186,21 @@ inline Env::operator napi_env() const { inline Object Env::Global() const { napi_value value; napi_status status = napi_get_global(*this, &value); - if (status != napi_ok) throw Error::New(*this); + NAPI_THROW_IF_FAILED(*this, status, Object()); return Object(*this, value); } inline Value Env::Undefined() const { napi_value value; napi_status status = napi_get_undefined(*this, &value); - if (status != napi_ok) throw Error::New(*this); + NAPI_THROW_IF_FAILED(*this, status, Value()); return Value(*this, value); } inline Value Env::Null() const { napi_value value; napi_status status = napi_get_null(*this, &value); - if (status != napi_ok) throw Error::New(*this); + NAPI_THROW_IF_FAILED(*this, status, Value()); return Value(*this, value); } @@ -182,6 +211,16 @@ inline bool Env::IsExceptionPending() const { return result; } +inline Error Env::GetAndClearPendingException() { + napi_value value; + napi_status status = napi_get_and_clear_last_exception(_env, &value); + if (status != napi_ok) { + // Don't throw another exception when failing to get the exception! + return Error(); + } + return Error(_env, value); +} + //////////////////////////////////////////////////////////////////////////////// // Value class //////////////////////////////////////////////////////////////////////////////// @@ -207,7 +246,7 @@ inline bool Value::operator !=(const Value& other) const { inline bool Value::StrictEquals(const Value& other) const { bool result; napi_status status = napi_strict_equals(_env, *this, other, &result); - if (status != napi_ok) throw Error::New(_env); + NAPI_THROW_IF_FAILED(_env, status, false); return result; } @@ -215,6 +254,10 @@ inline Napi::Env Value::Env() const { return Napi::Env(_env); } +inline bool Value::IsEmpty() const { + return _value == nullptr; +} + inline napi_valuetype Value::Type() const { if (_value == nullptr) { return napi_undefined; @@ -222,7 +265,7 @@ inline napi_valuetype Value::Type() const { napi_valuetype type; napi_status status = napi_typeof(_env, _value, &type); - if (status != napi_ok) throw Error::New(_env); + NAPI_THROW_IF_FAILED(_env, status, napi_undefined); return type; } @@ -257,7 +300,7 @@ inline bool Value::IsArray() const { bool result; napi_status status = napi_is_array(_env, _value, &result); - if (status != napi_ok) throw Error::New(_env); + NAPI_THROW_IF_FAILED(_env, status, false); return result; } @@ -268,7 +311,7 @@ inline bool Value::IsArrayBuffer() const { bool result; napi_status status = napi_is_arraybuffer(_env, _value, &result); - if (status != napi_ok) throw Error::New(_env); + NAPI_THROW_IF_FAILED(_env, status, false); return result; } @@ -279,7 +322,7 @@ inline bool Value::IsTypedArray() const { bool result; napi_status status = napi_is_typedarray(_env, _value, &result); - if (status != napi_ok) throw Error::New(_env); + NAPI_THROW_IF_FAILED(_env, status, false); return result; } @@ -298,7 +341,7 @@ inline bool Value::IsBuffer() const { bool result; napi_status status = napi_is_buffer(_env, _value, &result); - if (status != napi_ok) throw Error::New(_env); + NAPI_THROW_IF_FAILED(_env, status, false); return result; } @@ -310,28 +353,28 @@ inline T Value::As() const { inline Boolean Value::ToBoolean() const { napi_value result; napi_status status = napi_coerce_to_bool(_env, _value, &result); - if (status != napi_ok) throw Error::New(_env); + NAPI_THROW_IF_FAILED(_env, status, Boolean()); return Boolean(_env, result); } inline Number Value::ToNumber() const { napi_value result; napi_status status = napi_coerce_to_number(_env, _value, &result); - if (status != napi_ok) throw Error::New(_env); + NAPI_THROW_IF_FAILED(_env, status, Number()); return Number(_env, result); } inline String Value::ToString() const { napi_value result; napi_status status = napi_coerce_to_string(_env, _value, &result); - if (status != napi_ok) throw Error::New(_env); + NAPI_THROW_IF_FAILED(_env, status, String()); return String(_env, result); } inline Object Value::ToObject() const { napi_value result; napi_status status = napi_coerce_to_object(_env, _value, &result); - if (status != napi_ok) throw Error::New(_env); + NAPI_THROW_IF_FAILED(_env, status, Object()); return Object(_env, result); } @@ -342,7 +385,7 @@ inline Object Value::ToObject() const { inline Boolean Boolean::New(napi_env env, bool val) { napi_value value; napi_status status = napi_get_boolean(env, val, &value); - if (status != napi_ok) throw Error::New(env); + NAPI_THROW_IF_FAILED(env, status, Boolean()); return Boolean(env, value); } @@ -359,7 +402,7 @@ inline Boolean::operator bool() const { inline bool Boolean::Value() const { bool result; napi_status status = napi_get_value_bool(_env, _value, &result); - if (status != napi_ok) throw Error::New(_env); + NAPI_THROW_IF_FAILED(_env, status, false); return result; } @@ -370,7 +413,7 @@ inline bool Boolean::Value() const { inline Number Number::New(napi_env env, double val) { napi_value value; napi_status status = napi_create_number(env, val, &value); - if (status != napi_ok) throw Error::New(env); + NAPI_THROW_IF_FAILED(env, status, Number()); return Number(env, value); } @@ -403,21 +446,21 @@ inline Number::operator double() const { inline int32_t Number::Int32Value() const { int32_t result; napi_status status = napi_get_value_int32(_env, _value, &result); - if (status != napi_ok) throw Error::New(_env); + NAPI_THROW_IF_FAILED(_env, status, 0); return result; } inline uint32_t Number::Uint32Value() const { uint32_t result; napi_status status = napi_get_value_uint32(_env, _value, &result); - if (status != napi_ok) throw Error::New(_env); + NAPI_THROW_IF_FAILED(_env, status, 0); return result; } inline int64_t Number::Int64Value() const { int64_t result; napi_status status = napi_get_value_int64(_env, _value, &result); - if (status != napi_ok) throw Error::New(_env); + NAPI_THROW_IF_FAILED(_env, status, 0); return result; } @@ -428,7 +471,7 @@ inline float Number::FloatValue() const { inline double Number::DoubleValue() const { double result; napi_status status = napi_get_value_double(_env, _value, &result); - if (status != napi_ok) throw Error::New(_env); + NAPI_THROW_IF_FAILED(_env, status, 0); return result; } @@ -457,28 +500,28 @@ inline String String::New(napi_env env, const std::u16string& val) { inline String String::New(napi_env env, const char* val) { napi_value value; napi_status status = napi_create_string_utf8(env, val, std::strlen(val), &value); - if (status != napi_ok) throw Error::New(env); + NAPI_THROW_IF_FAILED(env, status, String()); return String(env, value); } inline String String::New(napi_env env, const char16_t* val) { napi_value value; napi_status status = napi_create_string_utf16(env, val, std::u16string(val).size(), &value); - if (status != napi_ok) throw Error::New(env); + NAPI_THROW_IF_FAILED(env, status, String()); return String(env, value); } inline String String::New(napi_env env, const char* val, size_t length) { napi_value value; napi_status status = napi_create_string_utf8(env, val, length, &value); - if (status != napi_ok) throw Error::New(env); + NAPI_THROW_IF_FAILED(env, status, String()); return String(env, value); } inline String String::New(napi_env env, const char16_t* val, size_t length) { napi_value value; napi_status status = napi_create_string_utf16(env, val, length, &value); - if (status != napi_ok) throw Error::New(env); + NAPI_THROW_IF_FAILED(env, status, String()); return String(env, value); } @@ -499,26 +542,26 @@ inline String::operator std::u16string() const { inline std::string String::Utf8Value() const { size_t length; napi_status status = napi_get_value_string_utf8(_env, _value, nullptr, 0, &length); - if (status != napi_ok) throw Error::New(_env); + NAPI_THROW_IF_FAILED(_env, status, ""); std::string value; value.reserve(length + 1); value.resize(length); status = napi_get_value_string_utf8(_env, _value, &value[0], value.capacity(), nullptr); - if (status != napi_ok) throw Error::New(_env); + NAPI_THROW_IF_FAILED(_env, status, ""); return value; } inline std::u16string String::Utf16Value() const { size_t length; napi_status status = napi_get_value_string_utf16(_env, _value, nullptr, 0, &length); - if (status != napi_ok) throw Error::New(_env); + NAPI_THROW_IF_FAILED(_env, status, u""); std::u16string value; value.reserve(length + 1); value.resize(length); status = napi_get_value_string_utf16(_env, _value, &value[0], value.capacity(), nullptr); - if (status != napi_ok) throw Error::New(_env); + NAPI_THROW_IF_FAILED(_env, status, u""); return value; } @@ -545,7 +588,7 @@ inline Symbol Symbol::New(napi_env env, String description) { inline Symbol Symbol::New(napi_env env, napi_value description) { napi_value value; napi_status status = napi_create_symbol(env, description, &value); - if (status != napi_ok) throw Error::New(env); + NAPI_THROW_IF_FAILED(env, status, Symbol()); return Symbol(env, value); } @@ -577,7 +620,7 @@ inline PropertyLValue::PropertyLValue(Object object, Key key) inline Object Object::New(napi_env env) { napi_value value; napi_status status = napi_create_object(env, &value); - if (status != napi_ok) throw Error::New(env); + NAPI_THROW_IF_FAILED(env, status, Object()); return Object(env, value); } @@ -614,21 +657,21 @@ inline Value Object::operator [](uint32_t index) const { inline bool Object::Has(napi_value name) const { bool result; napi_status status = napi_has_property(_env, _value, name, &result); - if (status != napi_ok) throw Error::New(_env); + NAPI_THROW_IF_FAILED(_env, status, false); return result; } inline bool Object::Has(Value name) const { bool result; napi_status status = napi_has_property(_env, _value, name, &result); - if (status != napi_ok) throw Error::New(_env); + NAPI_THROW_IF_FAILED(_env, status, false); return result; } inline bool Object::Has(const char* utf8name) const { bool result; napi_status status = napi_has_named_property(_env, _value, utf8name, &result); - if (status != napi_ok) throw Error::New(Env()); + NAPI_THROW_IF_FAILED(_env, status, false); return result; } @@ -639,21 +682,21 @@ inline bool Object::Has(const std::string& utf8name) const { inline Value Object::Get(napi_value name) const { napi_value result; napi_status status = napi_get_property(_env, _value, name, &result); - if (status != napi_ok) throw Error::New(_env); + NAPI_THROW_IF_FAILED(_env, status, Value()); return Value(_env, result); } inline Value Object::Get(Value name) const { napi_value result; napi_status status = napi_get_property(_env, _value, name, &result); - if (status != napi_ok) throw Error::New(_env); + NAPI_THROW_IF_FAILED(_env, status, Value()); return Value(_env, result); } inline Value Object::Get(const char* utf8name) const { napi_value result; napi_status status = napi_get_named_property(_env, _value, utf8name, &result); - if (status != napi_ok) throw Error::New(Env()); + NAPI_THROW_IF_FAILED(_env, status, Value()); return Value(_env, result); } @@ -663,17 +706,17 @@ inline Value Object::Get(const std::string& utf8name) const { inline void Object::Set(napi_value name, napi_value value) { napi_status status = napi_set_property(_env, _value, name, value); - if (status != napi_ok) throw Error::New(_env); + NAPI_THROW_IF_FAILED(_env, status); } inline void Object::Set(const char* utf8name, Value value) { napi_status status = napi_set_named_property(_env, _value, utf8name, value); - if (status != napi_ok) throw Error::New(Env()); + NAPI_THROW_IF_FAILED(_env, status); } inline void Object::Set(const char* utf8name, napi_value value) { napi_status status = napi_set_named_property(_env, _value, utf8name, value); - if (status != napi_ok) throw Error::New(Env()); + NAPI_THROW_IF_FAILED(_env, status); } inline void Object::Set(const char* utf8name, const char* utf8value) { @@ -711,25 +754,25 @@ inline void Object::Set(const std::string& utf8name, double numberValue) { inline bool Object::Has(uint32_t index) const { bool result; napi_status status = napi_has_element(_env, _value, index, &result); - if (status != napi_ok) throw Error::New(_env); + NAPI_THROW_IF_FAILED(_env, status, false); return result; } inline Value Object::Get(uint32_t index) const { napi_value value; napi_status status = napi_get_element(_env, _value, index, &value); - if (status != napi_ok) throw Error::New(_env); + NAPI_THROW_IF_FAILED(_env, status, Value()); return Value(_env, value); } inline void Object::Set(uint32_t index, napi_value value) { napi_status status = napi_set_element(_env, _value, index, value); - if (status != napi_ok) throw Error::New(_env); + NAPI_THROW_IF_FAILED(_env, status); } inline void Object::Set(uint32_t index, Value value) { napi_status status = napi_set_element(_env, _value, index, value); - if (status != napi_ok) throw Error::New(_env); + NAPI_THROW_IF_FAILED(_env, status); } inline void Object::Set(uint32_t index, const char* utf8value) { @@ -751,25 +794,25 @@ inline void Object::Set(uint32_t index, double numberValue) { inline void Object::DefineProperty(const PropertyDescriptor& property) { napi_status status = napi_define_properties(_env, _value, 1, reinterpret_cast(&property)); - if (status != napi_ok) throw Error::New(_env); + NAPI_THROW_IF_FAILED(_env, status); } inline void Object::DefineProperties(const std::initializer_list& properties) { napi_status status = napi_define_properties(_env, _value, properties.size(), reinterpret_cast(properties.begin())); - if (status != napi_ok) throw Error::New(_env); + NAPI_THROW_IF_FAILED(_env, status); } inline void Object::DefineProperties(const std::vector& properties) { napi_status status = napi_define_properties(_env, _value, properties.size(), reinterpret_cast(properties.data())); - if (status != napi_ok) throw Error::New(_env); + NAPI_THROW_IF_FAILED(_env, status); } inline bool Object::InstanceOf(const Function& constructor) const { bool result; napi_status status = napi_instanceof(_env, _value, constructor, &result); - if (status != napi_ok) throw Error::New(_env); + NAPI_THROW_IF_FAILED(_env, status, false); return result; } @@ -781,7 +824,7 @@ template inline External External::New(napi_env env, T* data) { napi_value value; napi_status status = napi_create_external(env, data, nullptr, nullptr, &value); - if (status != napi_ok) throw Error::New(env); + NAPI_THROW_IF_FAILED(env, status, External()); return External(env, value); } @@ -801,7 +844,7 @@ inline External External::New(napi_env env, &value); if (status != napi_ok) { delete finalizeData; - throw Error::New(env); + NAPI_THROW_IF_FAILED(env, status, External()); } return External(env, value); } @@ -823,7 +866,7 @@ inline External External::New(napi_env env, &value); if (status != napi_ok) { delete finalizeData; - throw Error::New(env); + NAPI_THROW_IF_FAILED(env, status, External()); } return External(env, value); } @@ -840,7 +883,7 @@ template inline T* External::Data() const { void* data; napi_status status = napi_get_value_external(_env, _value, &data); - if (status != napi_ok) throw Error::New(_env); + NAPI_THROW_IF_FAILED(_env, status, nullptr); return reinterpret_cast(data); } @@ -851,14 +894,14 @@ inline T* External::Data() const { inline Array Array::New(napi_env env) { napi_value value; napi_status status = napi_create_array(env, &value); - if (status != napi_ok) throw Error::New(env); + NAPI_THROW_IF_FAILED(env, status, Array()); return Array(env, value); } inline Array Array::New(napi_env env, size_t length) { napi_value value; napi_status status = napi_create_array_with_length(env, length, &value); - if (status != napi_ok) throw Error::New(env); + NAPI_THROW_IF_FAILED(env, status, Array()); return Array(env, value); } @@ -871,7 +914,7 @@ inline Array::Array(napi_env env, napi_value value) : Object(env, value) { inline uint32_t Array::Length() const { uint32_t result; napi_status status = napi_get_array_length(_env, _value, &result); - if (status != napi_ok) throw Error::New(_env); + NAPI_THROW_IF_FAILED(_env, status, 0); return result; } @@ -883,7 +926,7 @@ inline ArrayBuffer ArrayBuffer::New(napi_env env, size_t byteLength) { napi_value value; void* data; napi_status status = napi_create_arraybuffer(env, byteLength, &data, &value); - if (status != napi_ok) throw Error::New(env); + NAPI_THROW_IF_FAILED(env, status, ArrayBuffer()); return ArrayBuffer(env, value, data, byteLength); } @@ -894,7 +937,7 @@ inline ArrayBuffer ArrayBuffer::New(napi_env env, napi_value value; napi_status status = napi_create_external_arraybuffer( env, externalData, byteLength, nullptr, nullptr, &value); - if (status != napi_ok) throw Error::New(env); + NAPI_THROW_IF_FAILED(env, status, ArrayBuffer()); return ArrayBuffer(env, value, externalData, byteLength); } @@ -916,7 +959,7 @@ inline ArrayBuffer ArrayBuffer::New(napi_env env, &value); if (status != napi_ok) { delete finalizeData; - throw Error::New(env); + NAPI_THROW_IF_FAILED(env, status, ArrayBuffer()); } return ArrayBuffer(env, value, externalData, byteLength); @@ -940,7 +983,7 @@ inline ArrayBuffer ArrayBuffer::New(napi_env env, &value); if (status != napi_ok) { delete finalizeData; - throw Error::New(env); + NAPI_THROW_IF_FAILED(env, status, ArrayBuffer()); } return ArrayBuffer(env, value, externalData, byteLength); @@ -973,7 +1016,7 @@ inline void ArrayBuffer::EnsureInfo() const { // since they can never change during the lifetime of the ArrayBuffer. if (_data == nullptr) { napi_status status = napi_get_arraybuffer_info(_env, _value, &_data, &_length); - if (status != napi_ok) throw Error::New(_env); + NAPI_THROW_IF_FAILED(_env, status); } } @@ -1001,7 +1044,7 @@ inline napi_typedarray_type TypedArray::TypedArrayType() const { napi_status status = napi_get_typedarray_info(_env, _value, &const_cast(this)->_type, &const_cast(this)->_length, nullptr, nullptr, nullptr); - if (status != napi_ok) throw Error::New(_env); + NAPI_THROW_IF_FAILED(_env, status, napi_int8_array); } return _type; @@ -1032,7 +1075,7 @@ inline size_t TypedArray::ElementLength() const { napi_status status = napi_get_typedarray_info(_env, _value, &const_cast(this)->_type, &const_cast(this)->_length, nullptr, nullptr, nullptr); - if (status != napi_ok) throw Error::New(_env); + NAPI_THROW_IF_FAILED(_env, status, 0); } return _length; @@ -1042,7 +1085,7 @@ inline size_t TypedArray::ByteOffset() const { size_t byteOffset; napi_status status = napi_get_typedarray_info( _env, _value, nullptr, nullptr, nullptr, nullptr, &byteOffset); - if (status != napi_ok) throw Error::New(_env); + NAPI_THROW_IF_FAILED(_env, status, 0); return byteOffset; } @@ -1054,7 +1097,7 @@ inline Napi::ArrayBuffer TypedArray::ArrayBuffer() const { napi_value arrayBuffer; napi_status status = napi_get_typedarray_info( _env, _value, nullptr, nullptr, nullptr, &arrayBuffer, nullptr); - if (status != napi_ok) throw Error::New(_env); + NAPI_THROW_IF_FAILED(_env, status, Napi::ArrayBuffer()); return Napi::ArrayBuffer(_env, arrayBuffer); } @@ -1079,7 +1122,7 @@ inline TypedArrayOf TypedArrayOf::New(napi_env env, napi_value value; napi_status status = napi_create_typedarray( env, type, elementLength, arrayBuffer, bufferOffset, &value); - if (status != napi_ok) throw Error::New(env); + NAPI_THROW_IF_FAILED(env, status, TypedArrayOf()); return TypedArrayOf( env, value, type, elementLength, @@ -1095,7 +1138,7 @@ inline TypedArrayOf::TypedArrayOf(napi_env env, napi_value value) : TypedArray(env, value), _data(nullptr) { napi_status status = napi_get_typedarray_info( _env, _value, &_type, &_length, reinterpret_cast(&_data), nullptr, nullptr); - if (status != napi_ok) throw Error::New(_env); + NAPI_THROW_IF_FAILED(_env, status); } template @@ -1107,8 +1150,8 @@ inline TypedArrayOf::TypedArrayOf(napi_env env, : TypedArray(env, value, type, length), _data(data) { if (!(type == TypedArrayTypeForPrimitiveType() || (type == napi_uint8_clamped_array && std::is_same::value))) { - throw TypeError::New(env, "Array type must match the template parameter. " - "(Uint8 arrays may optionally have the \"clamped\" array type.)"); + NAPI_THROW(TypeError::New(env, "Array type must match the template parameter. " + "(Uint8 arrays may optionally have the \"clamped\" array type.)")); } } @@ -1149,7 +1192,7 @@ inline Function Function::New(napi_env env, napi_value value; napi_status status = napi_create_function( env, utf8name, CbData::Wrapper, callbackData, &value); - if (status != napi_ok) throw Error::New(env); + NAPI_THROW_IF_FAILED(env, status, Function()); return Function(env, value); } @@ -1183,7 +1226,7 @@ inline Value Function::Call(napi_value recv, const std::initializer_list& args napi_value result; napi_status status = napi_call_function( _env, recv, _value, args.size(), args.data(), &result); - if (status != napi_ok) throw Error::New(_env); + NAPI_THROW_IF_FAILED(_env, status, Value()); return Value(_env, result); } @@ -1200,7 +1243,7 @@ inline Value Function::MakeCallback( napi_value result; napi_status status = napi_make_callback( _env, recv, _value, args.size(), args.begin(), &result); - if (status != napi_ok) throw Error::New(_env); + NAPI_THROW_IF_FAILED(_env, status, Value()); return Value(_env, result); } @@ -1209,7 +1252,7 @@ inline Value Function::MakeCallback( napi_value result; napi_status status = napi_make_callback( _env, recv, _value, args.size(), args.data(), &result); - if (status != napi_ok) throw Error::New(_env); + NAPI_THROW_IF_FAILED(_env, status, Value()); return Value(_env, result); } @@ -1217,7 +1260,7 @@ inline Object Function::New(const std::initializer_list& args) const napi_value result; napi_status status = napi_new_instance( _env, _value, args.size(), args.begin(), &result); - if (status != napi_ok) throw Error::New(_env); + NAPI_THROW_IF_FAILED(_env, status, Object()); return Object(_env, result); } @@ -1225,7 +1268,7 @@ inline Object Function::New(const std::vector& args) const { napi_value result; napi_status status = napi_new_instance( _env, _value, args.size(), args.data(), &result); - if (status != napi_ok) throw Error::New(_env); + NAPI_THROW_IF_FAILED(_env, status, Object()); return Object(_env, result); } @@ -1238,7 +1281,7 @@ inline Buffer Buffer::New(napi_env env, size_t length) { napi_value value; void* data; napi_status status = napi_create_buffer(env, length * sizeof (T), &data, &value); - if (status != napi_ok) throw Error::New(env); + NAPI_THROW_IF_FAILED(env, status, Buffer()); return Buffer(env, value, length, static_cast(data)); } @@ -1247,7 +1290,7 @@ inline Buffer Buffer::New(napi_env env, T* data, size_t length) { napi_value value; napi_status status = napi_create_external_buffer( env, length * sizeof (T), data, nullptr, nullptr, &value); - if (status != napi_ok) throw Error::New(env); + NAPI_THROW_IF_FAILED(env, status, Buffer()); return Buffer(env, value, length, data); } @@ -1269,7 +1312,7 @@ inline Buffer Buffer::New(napi_env env, &value); if (status != napi_ok) { delete finalizeData; - throw Error::New(env); + NAPI_THROW_IF_FAILED(env, status, Buffer()); } return Buffer(env, value, length, data); } @@ -1293,7 +1336,7 @@ inline Buffer Buffer::New(napi_env env, &value); if (status != napi_ok) { delete finalizeData; - throw Error::New(env); + NAPI_THROW_IF_FAILED(env, status, Buffer()); } return Buffer(env, value, length, data); } @@ -1303,7 +1346,7 @@ inline Buffer Buffer::Copy(napi_env env, const T* data, size_t length) { napi_value value; napi_status status = napi_create_buffer_copy( env, length * sizeof (T), data, nullptr, &value); - if (status != napi_ok) throw Error::New(env); + NAPI_THROW_IF_FAILED(env, status, Buffer()); return Buffer(env, value); } @@ -1342,7 +1385,7 @@ inline void Buffer::EnsureInfo() const { size_t byteLength; void* voidData; napi_status status = napi_get_buffer_info(_env, _value, &voidData, &byteLength); - if (status != napi_ok) throw Error::New(_env); + NAPI_THROW_IF_FAILED(_env, status); _length = byteLength / sizeof (T); _data = static_cast(voidData); } @@ -1368,6 +1411,16 @@ inline Error Error::New(napi_env env) { else { const char* error_message = info->error_message != nullptr ? info->error_message : "Error in native callback"; + + bool isExceptionPending; + status = napi_is_exception_pending(env, &isExceptionPending); + assert(status == napi_ok); + + if (isExceptionPending) { + status = napi_get_and_clear_last_exception(env, &error); + assert(status == napi_ok); + } + napi_value message; status = napi_create_string_utf8( env, @@ -1437,7 +1490,7 @@ inline Error& Error::operator =(Error& other) { napi_value value = other.Value(); if (value != nullptr) { napi_status status = napi_create_reference(_env, value, 1, &_ref); - if (status != napi_ok) throw Error::New(_env); + NAPI_THROW_IF_FAILED(_env, status, *this); } return *this; @@ -1445,14 +1498,17 @@ inline Error& Error::operator =(Error& other) { inline const std::string& Error::Message() const NAPI_NOEXCEPT { if (_message.size() == 0 && _env != nullptr) { +#ifdef NAPI_CPP_EXCEPTIONS try { _message = Get("message").As(); } catch (...) { // Catch all errors here, to include e.g. a std::bad_alloc from - // the std::string::operator=, because this is used by the - // Error::what() noexcept method. + // the std::string::operator=, because this method may not throw. } +#else // NAPI_CPP_EXCEPTIONS + _message = Get("message").As(); +#endif // NAPI_CPP_EXCEPTIONS } return _message; } @@ -1461,14 +1517,18 @@ inline void Error::ThrowAsJavaScriptException() const { HandleScope scope(_env); if (!IsEmpty()) { napi_status status = napi_throw(_env, Value()); - if (status != napi_ok) throw Error::New(_env); + NAPI_THROW_IF_FAILED(_env, status); } } +#ifdef NAPI_CPP_EXCEPTIONS + inline const char* Error::what() const NAPI_NOEXCEPT { return Message().c_str(); } +#endif // NAPI_CPP_EXCEPTIONS + template inline TError Error::New(napi_env env, const char* message, @@ -1476,11 +1536,11 @@ inline TError Error::New(napi_env env, create_error_fn create_error) { napi_value str; napi_status status = napi_create_string_utf8(env, message, length, &str); - if (status != napi_ok) throw Error::New(env); + NAPI_THROW_IF_FAILED(env, status, TError()); napi_value error; status = create_error(env, str, &error); - if (status != napi_ok) throw Error::New(env); + NAPI_THROW_IF_FAILED(env, status, TError()); return TError(env, error); } @@ -1528,7 +1588,7 @@ inline Reference Reference::New(const T& value, uint32_t initialRefcount) napi_ref ref; napi_status status = napi_create_reference(env, value, initialRefcount, &ref); - if (status != napi_ok) throw Error::New(napi_env(env)); + NAPI_THROW_IF_FAILED(env, status, Reference()); return Reference(env, ref); } @@ -1606,7 +1666,7 @@ inline T Reference::Value() const { napi_value value; napi_status status = napi_get_reference_value(_env, _ref, &value); - if (status != napi_ok) throw Error::New(_env); + NAPI_THROW_IF_FAILED(_env, status, T()); return T(_env, value); } @@ -1614,7 +1674,7 @@ template inline uint32_t Reference::Ref() { uint32_t result; napi_status status = napi_reference_ref(_env, _ref, &result); - if (status != napi_ok) throw Error::New(_env); + NAPI_THROW_IF_FAILED(_env, status, 1); return result; } @@ -1622,7 +1682,7 @@ template inline uint32_t Reference::Unref() { uint32_t result; napi_status status = napi_reference_unref(_env, _ref, &result); - if (status != napi_ok) throw Error::New(_env); + NAPI_THROW_IF_FAILED(_env, status, 1); return result; } @@ -1630,7 +1690,7 @@ template inline void Reference::Reset() { if (_ref != nullptr) { napi_status status = napi_delete_reference(_env, _ref); - if (status != napi_ok) throw Error::New(_env); + NAPI_THROW_IF_FAILED(_env, status); _ref = nullptr; } } @@ -1643,7 +1703,7 @@ inline void Reference::Reset(const T& value, uint32_t refcount) { napi_value val = value; if (val != nullptr) { napi_status status = napi_create_reference(_env, value, refcount, &_ref); - if (status != napi_ok) throw Error::New(_env); + NAPI_THROW_IF_FAILED(_env, status); } } @@ -1889,7 +1949,7 @@ inline CallbackInfo::CallbackInfo(napi_env env, napi_callback_info info) _argc = _staticArgCount; _argv = _staticArgs; napi_status status = napi_get_cb_info(env, info, &_argc, _argv, &_this, &_data); - if (status != napi_ok) throw Error::New(_env); + NAPI_THROW_IF_FAILED(_env, status); if (_argc > _staticArgCount) { // Use either a fixed-size array (on the stack) or a dynamically-allocated @@ -1898,7 +1958,7 @@ inline CallbackInfo::CallbackInfo(napi_env env, napi_callback_info info) _argv = _dynamicArgs; status = napi_get_cb_info(env, info, &_argc, _argv, nullptr, nullptr); - if (status != napi_ok) throw Error::New(_env); + NAPI_THROW_IF_FAILED(_env, status); } } @@ -2177,7 +2237,7 @@ template inline T* ObjectWrap::Unwrap(Object wrapper) { T* unwrapped; napi_status status = napi_unwrap(wrapper.Env(), wrapper, reinterpret_cast(&unwrapped)); - if (status != napi_ok) throw Error::New(wrapper.Env()); + NAPI_THROW_IF_FAILED(wrapper.Env(), status, nullptr); return unwrapped; } @@ -2191,7 +2251,7 @@ inline Function ObjectWrap::DefineClass( napi_status status = napi_define_class( env, utf8name, T::ConstructorCallbackWrapper, data, properties.size(), reinterpret_cast(properties.begin()), &value); - if (status != napi_ok) throw Error::New(env); + NAPI_THROW_IF_FAILED(env, status, Function()); return Function(env, value); } @@ -2206,7 +2266,7 @@ inline Function ObjectWrap::DefineClass( napi_status status = napi_define_class( env, utf8name, T::ConstructorCallbackWrapper, data, properties.size(), reinterpret_cast(properties.data()), &value); - if (status != napi_ok) throw Error::New(env); + NAPI_THROW_IF_FAILED(env, status, Function()); return Function(env, value); } @@ -2357,12 +2417,11 @@ inline napi_value ObjectWrap::ConstructorCallbackWrapper( T* instance; napi_value wrapper; - try { + details::WrapCallback([&] { CallbackInfo callbackInfo(env, info); instance = new T(callbackInfo); wrapper = callbackInfo.This(); - } - NAPI_RETHROW_JS_ERROR(env) + }); napi_ref ref; status = napi_wrap(env, wrapper, instance, FinalizeCallback, nullptr, &ref); @@ -2378,65 +2437,61 @@ template inline napi_value ObjectWrap::StaticVoidMethodCallbackWrapper( napi_env env, napi_callback_info info) { - try { + return details::WrapCallback([&] { CallbackInfo callbackInfo(env, info); StaticVoidMethodCallbackData* callbackData = reinterpret_cast(callbackInfo.Data()); callbackInfo.SetData(callbackData->data); callbackData->callback(callbackInfo); return nullptr; - } - NAPI_RETHROW_JS_ERROR(env) + }); } template inline napi_value ObjectWrap::StaticMethodCallbackWrapper( napi_env env, napi_callback_info info) { - try { + return details::WrapCallback([&] { CallbackInfo callbackInfo(env, info); StaticMethodCallbackData* callbackData = reinterpret_cast(callbackInfo.Data()); callbackInfo.SetData(callbackData->data); return callbackData->callback(callbackInfo); - } - NAPI_RETHROW_JS_ERROR(env) + }); } template inline napi_value ObjectWrap::StaticGetterCallbackWrapper( napi_env env, napi_callback_info info) { - try { + return details::WrapCallback([&] { CallbackInfo callbackInfo(env, info); StaticAccessorCallbackData* callbackData = reinterpret_cast(callbackInfo.Data()); callbackInfo.SetData(callbackData->data); return callbackData->getterCallback(callbackInfo); - } - NAPI_RETHROW_JS_ERROR(env) + }); } template inline napi_value ObjectWrap::StaticSetterCallbackWrapper( napi_env env, napi_callback_info info) { - try { + return details::WrapCallback([&] { CallbackInfo callbackInfo(env, info); StaticAccessorCallbackData* callbackData = reinterpret_cast(callbackInfo.Data()); callbackInfo.SetData(callbackData->data); callbackData->setterCallback(callbackInfo, callbackInfo[0]); return nullptr; - } - NAPI_RETHROW_JS_ERROR(env) + }); } template inline napi_value ObjectWrap::InstanceVoidMethodCallbackWrapper( napi_env env, napi_callback_info info) { - try { + return details::WrapCallback([&] { CallbackInfo callbackInfo(env, info); InstanceVoidMethodCallbackData* callbackData = reinterpret_cast(callbackInfo.Data()); @@ -2445,15 +2500,14 @@ inline napi_value ObjectWrap::InstanceVoidMethodCallbackWrapper( auto cb = callbackData->callback; (instance->*cb)(callbackInfo); return nullptr; - } - NAPI_RETHROW_JS_ERROR(env) + }); } template inline napi_value ObjectWrap::InstanceMethodCallbackWrapper( napi_env env, napi_callback_info info) { - try { + return details::WrapCallback([&] { CallbackInfo callbackInfo(env, info); InstanceMethodCallbackData* callbackData = reinterpret_cast(callbackInfo.Data()); @@ -2461,15 +2515,14 @@ inline napi_value ObjectWrap::InstanceMethodCallbackWrapper( T* instance = Unwrap(callbackInfo.This().As()); auto cb = callbackData->callback; return (instance->*cb)(callbackInfo); - } - NAPI_RETHROW_JS_ERROR(env) + }); } template inline napi_value ObjectWrap::InstanceGetterCallbackWrapper( napi_env env, napi_callback_info info) { - try { + return details::WrapCallback([&] { CallbackInfo callbackInfo(env, info); InstanceAccessorCallbackData* callbackData = reinterpret_cast(callbackInfo.Data()); @@ -2477,15 +2530,14 @@ inline napi_value ObjectWrap::InstanceGetterCallbackWrapper( T* instance = Unwrap(callbackInfo.This().As()); auto cb = callbackData->getterCallback; return (instance->*cb)(callbackInfo); - } - NAPI_RETHROW_JS_ERROR(env) + }); } template inline napi_value ObjectWrap::InstanceSetterCallbackWrapper( napi_env env, napi_callback_info info) { - try { + return details::WrapCallback([&] { CallbackInfo callbackInfo(env, info); InstanceAccessorCallbackData* callbackData = reinterpret_cast(callbackInfo.Data()); @@ -2494,8 +2546,7 @@ inline napi_value ObjectWrap::InstanceSetterCallbackWrapper( auto cb = callbackData->setterCallback; (instance->*cb)(callbackInfo, callbackInfo[0]); return nullptr; - } - NAPI_RETHROW_JS_ERROR(env) + }); } template @@ -2514,7 +2565,7 @@ inline HandleScope::HandleScope(napi_env env, napi_handle_scope scope) inline HandleScope::HandleScope(Napi::Env env) : _env(env) { napi_status status = napi_open_handle_scope(_env, &_scope); - if (status != napi_ok) throw Error::New(_env); + NAPI_THROW_IF_FAILED(_env, status); } inline HandleScope::~HandleScope() { @@ -2539,7 +2590,7 @@ inline EscapableHandleScope::EscapableHandleScope( inline EscapableHandleScope::EscapableHandleScope(Napi::Env env) : _env(env) { napi_status status = napi_open_escapable_handle_scope(_env, &_scope); - if (status != napi_ok) throw Error::New(_env); + NAPI_THROW_IF_FAILED(_env, status); } inline EscapableHandleScope::~EscapableHandleScope() { @@ -2557,7 +2608,7 @@ inline Napi::Env EscapableHandleScope::Env() const { inline Value EscapableHandleScope::Escape(napi_value escapee) { napi_value result; napi_status status = napi_escape_handle(_env, _scope, escapee, &result); - if (status != napi_ok) throw Error::New(_env); + NAPI_THROW_IF_FAILED(_env, status, Value()); return Value(_env, result); } @@ -2575,7 +2626,7 @@ inline AsyncWorker::AsyncWorker(const Object& receiver, const Function& callback _callback(Napi::Persistent(callback)) { napi_status status = napi_create_async_work( _env, OnExecute, OnWorkComplete, this, &_work); - if (status != napi_ok) throw Error::New(_env); + NAPI_THROW_IF_FAILED(_env, status); } inline AsyncWorker::~AsyncWorker() { @@ -2616,12 +2667,12 @@ inline Napi::Env AsyncWorker::Env() const { inline void AsyncWorker::Queue() { napi_status status = napi_queue_async_work(_env, _work); - if (status != napi_ok) throw Error::New(_env); + NAPI_THROW_IF_FAILED(_env, status); } inline void AsyncWorker::Cancel() { napi_status status = napi_cancel_async_work(_env, _work); - if (status != napi_ok) throw Error::New(_env); + NAPI_THROW_IF_FAILED(_env, status); } inline ObjectReference& AsyncWorker::Receiver() { @@ -2646,11 +2697,15 @@ inline void AsyncWorker::SetError(const std::string& error) { inline void AsyncWorker::OnExecute(napi_env env, void* this_pointer) { AsyncWorker* self = static_cast(this_pointer); +#ifdef NAPI_CPP_EXCEPTIONS try { self->Execute(); } catch (const std::exception& e) { self->SetError(e.what()); } +#else // NAPI_CPP_EXCEPTIONS + self->Execute(); +#endif // NAPI_CPP_EXCEPTIONS } inline void AsyncWorker::OnWorkComplete( @@ -2658,23 +2713,22 @@ inline void AsyncWorker::OnWorkComplete( AsyncWorker* self = static_cast(this_pointer); if (status != napi_cancelled) { HandleScope scope(self->_env); - try { + details::WrapCallback([&] { if (self->_error.size() == 0) { self->OnOK(); } else { self->OnError(Error::New(self->_env, self->_error)); } - } catch (const Error& e) { - e.ThrowAsJavaScriptException(); - } + return nullptr; + }); } delete self; } -// This macro shouldn't be useful in user code, because all -// callbacks from JavaScript to C++ are wrapped here. -#undef NAPI_RETHROW_JS_ERROR +// These macros shouldn't be useful in user code. +#undef NAPI_THROW +#undef NAPI_THROW_IF_FAILED } // namespace Napi diff --git a/napi.h b/napi.h index 3eb9d53f2..a57ed94b2 100644 --- a/napi.h +++ b/napi.h @@ -7,6 +7,17 @@ #include #include +// If C++ exceptions are not explicitly enabled or disabled, enable them +// if exceptions were enabled in the compiler settings. +#if !defined(NAPI_CPP_EXCEPTIONS) && !defined(NAPI_DISABLE_CPP_EXCEPTIONS) + #if defined(_CPPUNWIND) || defined (__EXCEPTIONS) + #define NAPI_CPP_EXCEPTIONS + #else + #error Exception support not detected. \ + Define either NAPI_CPP_EXCEPTIONS or NAPI_DISABLE_CPP_EXCEPTIONS. + #endif +#endif + #ifdef _NOEXCEPT #define NAPI_NOEXCEPT _NOEXCEPT #else @@ -74,6 +85,7 @@ namespace Napi { Value Null() const; bool IsExceptionPending() const; + Error GetAndClearPendingException(); private: napi_env _env; @@ -112,6 +124,17 @@ namespace Napi { /// Gets the environment the value is associated with. Napi::Env Env() const; + /// Checks if the value is empty (uninitialized). + /// + /// An empty value is invalid, and most attempts to perform an operation on an empty value + /// will result in an exception. Note an empty value is distinct from JavaScript `null` or + /// `undefined`, which are valid values. + /// + /// When C++ exceptions are disabled at compile time, a method with a `Value` return type may + /// return an empty value to indicate a pending exception. So when not using C++ exceptions, + /// callers should check whether the value is empty before attempting to use it. + bool IsEmpty() const; + napi_valuetype Type() const; ///< Gets the type of the value. bool IsUndefined() const; ///< Tests if a value is an undefined JavaScript value. @@ -698,8 +721,13 @@ namespace Napi { ObjectReference Persistent(Object value); FunctionReference Persistent(Function value); - /// Wraps a JavaScript error object in a way that enables it to traverse a C++ stack and be - /// thrown and caught as a C++ exception. + /// A persistent reference to a JavaScript error object. Use of this class depends somewhat + /// on whether C++ exceptions are enabled at compile time. + /// + /// ### Handling Errors With C++ Exceptions + /// + /// If C++ exceptions are enabled, then the `Error` class extends `std::exception` and enables + /// integrated error-handling for C++ exceptions and JavaScript exceptions. /// /// If a N-API call fails without executing any JavaScript code (for example due to an invalid /// argument), then the N-API wrapper automatically converts and throws the error as a C++ @@ -711,35 +739,81 @@ namespace Napi { /// wrapper automatically converts and throws it as a JavaScript exception. Therefore, catching /// a C++ exception of type `Napi::Error` prevents a JavaScript exception from being thrown. /// - /// #### Example 1 - Throwing an exception: + /// #### Example 1A - Throwing a C++ exception: /// /// Napi::Env env = ... /// throw Napi::Error::New(env, "Example exception"); /// /// Following C++ statements will not be executed. The exception will bubble up as a C++ /// exception of type `Napi::Error`, until it is either caught while still in C++, or else - /// automatically re-thrown as a JavaScript exception when the callback returns to JavaScript. + /// automatically propataged as a JavaScript exception when the callback returns to JavaScript. /// - /// #### Example 2 - Not catching a N-API exception: + /// #### Example 2A - Propagating a N-API C++ exception: /// /// Napi::Function jsFunctionThatThrows = someObj.As(); - /// jsFunctionThatThrows({ arg1, arg2 }); + /// Napi::Value result = jsFunctionThatThrows({ arg1, arg2 }); /// /// Following C++ statements will not be executed. The exception will bubble up as a C++ /// exception of type `Napi::Error`, until it is either caught while still in C++, or else - /// automatically re-thrown as a JavaScript exception when the callback returns to JavaScript. + /// automatically propagated as a JavaScript exception when the callback returns to JavaScript. /// - /// #### Example 3 - Handling a N-API exception: + /// #### Example 3A - Handling a N-API C++ exception: /// /// Napi::Function jsFunctionThatThrows = someObj.As(); + /// Napi::Value result; /// try { - /// jsFunctionThatThrows({ arg1, arg2 }); + /// result = jsFunctionThatThrows({ arg1, arg2 }); /// } catch (const Napi::Error& e) { /// cerr << "Caught JavaScript exception: " + e.what(); /// } /// - /// Since the exception was caught here, it will not be re-thrown as a JavaScript exception. - class Error : public ObjectReference, public std::exception { + /// Since the exception was caught here, it will not be propagated as a JavaScript exception. + /// + /// ### Handling Errors Without C++ Exceptions + /// + /// If C++ exceptions are disabled (by defining `NAPI_DISABLE_CPP_EXCEPTIONS`) then this class + /// does not extend `std::exception`, and APIs in the `Napi` namespace do not throw C++ + /// exceptions when they fail. Instead, they raise _pending_ JavaScript exceptions and + /// return _empty_ `Value`s. Calling code should check `Value::IsEmpty()` before attempting + /// to use a returned value, and may use methods on the `Env` class to check for, get, and + /// clear a pending JavaScript exception. If the pending exception is not cleared, it will + /// be thrown when the native callback returns to JavaScript. + /// + /// #### Example 1B - Throwing a JS exception + /// + /// Napi::Env env = ... + /// Napi::Error::New(env, "Example exception").ThrowAsJavaScriptException(); + /// return; + /// + /// After throwing a JS exception, the code should generally return immediately from the native + /// callback, after performing any necessary cleanup. + /// + /// #### Example 2B - Propagating a N-API JS exception: + /// + /// Napi::Function jsFunctionThatThrows = someObj.As(); + /// Napi::Value result = jsFunctionThatThrows({ arg1, arg2 }); + /// if (result.IsEmpty()) return; + /// + /// An empty value result from a N-API call indicates an error occurred, and 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. + /// + /// #### Example 3B - Handling a N-API JS exception: + /// + /// Napi::Function jsFunctionThatThrows = someObj.As(); + /// Napi::Value result = jsFunctionThatThrows({ arg1, arg2 }); + /// if (result.IsEmpty()) { + /// 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. + class Error : public ObjectReference +#ifdef NAPI_CPP_EXCEPTIONS + , public std::exception +#endif // NAPI_CPP_EXCEPTIONS + { public: static Error New(napi_env env); static Error New(napi_env env, const char* message); @@ -757,7 +831,9 @@ namespace Napi { const std::string& Message() const NAPI_NOEXCEPT; void ThrowAsJavaScriptException() const; +#ifdef NAPI_CPP_EXCEPTIONS const char* what() const NAPI_NOEXCEPT override; +#endif // NAPI_CPP_EXCEPTIONS protected: /// !cond INTERNAL diff --git a/package.json b/package.json index 903e43bbf..60a1cfb06 100644 --- a/package.json +++ b/package.json @@ -31,5 +31,5 @@ "pretest": "node-gyp rebuild -C test", "test": "node test" }, - "version": "0.3.1" + "version": "0.3.2" } diff --git a/test/arraybuffer.cc b/test/arraybuffer.cc index 32caad279..b03492d43 100644 --- a/test/arraybuffer.cc +++ b/test/arraybuffer.cc @@ -27,7 +27,8 @@ Value CreateBuffer(const CallbackInfo& info) { ArrayBuffer buffer = ArrayBuffer::New(info.Env(), testLength); if (buffer.ByteLength() != testLength) { - throw Error::New(info.Env(), "Incorrect buffer length."); + Error::New(info.Env(), "Incorrect buffer length.").ThrowAsJavaScriptException(); + return Value(); } InitData(static_cast(buffer.Data()), testLength); @@ -40,11 +41,13 @@ Value CreateExternalBuffer(const CallbackInfo& info) { ArrayBuffer buffer = ArrayBuffer::New(info.Env(), testData, testLength); if (buffer.ByteLength() != testLength) { - throw Error::New(info.Env(), "Incorrect buffer length."); + Error::New(info.Env(), "Incorrect buffer length.").ThrowAsJavaScriptException(); + return Value(); } if (buffer.Data() != testData) { - throw Error::New(info.Env(), "Incorrect buffer data."); + Error::New(info.Env(), "Incorrect buffer data.").ThrowAsJavaScriptException(); + return Value(); } InitData(testData, testLength); @@ -66,11 +69,13 @@ Value CreateExternalBufferWithFinalize(const CallbackInfo& info) { }); if (buffer.ByteLength() != testLength) { - throw Error::New(info.Env(), "Incorrect buffer length."); + Error::New(info.Env(), "Incorrect buffer length.").ThrowAsJavaScriptException(); + return Value(); } if (buffer.Data() != data) { - throw Error::New(info.Env(), "Incorrect buffer data."); + Error::New(info.Env(), "Incorrect buffer data.").ThrowAsJavaScriptException(); + return Value(); } InitData(data, testLength); @@ -94,11 +99,13 @@ Value CreateExternalBufferWithFinalizeHint(const CallbackInfo& info) { hint); if (buffer.ByteLength() != testLength) { - throw Error::New(info.Env(), "Incorrect buffer length."); + Error::New(info.Env(), "Incorrect buffer length.").ThrowAsJavaScriptException(); + return Value(); } if (buffer.Data() != data) { - throw Error::New(info.Env(), "Incorrect buffer data."); + Error::New(info.Env(), "Incorrect buffer data.").ThrowAsJavaScriptException(); + return Value(); } InitData(data, testLength); @@ -107,17 +114,20 @@ Value CreateExternalBufferWithFinalizeHint(const CallbackInfo& info) { void CheckBuffer(const CallbackInfo& info) { if (!info[0].IsArrayBuffer()) { - throw Error::New(info.Env(), "A buffer was expected."); + Error::New(info.Env(), "A buffer was expected.").ThrowAsJavaScriptException(); + return; } ArrayBuffer buffer = info[0].As(); if (buffer.ByteLength() != testLength) { - throw Error::New(info.Env(), "Incorrect buffer length."); + Error::New(info.Env(), "Incorrect buffer length.").ThrowAsJavaScriptException(); + return; } if (!VerifyData(static_cast(buffer.Data()), testLength)) { - throw Error::New(info.Env(), "Incorrect buffer data."); + Error::New(info.Env(), "Incorrect buffer data.").ThrowAsJavaScriptException(); + return; } } diff --git a/test/arraybuffer.js b/test/arraybuffer.js index f873b0d1e..d284fe84a 100644 --- a/test/arraybuffer.js +++ b/test/arraybuffer.js @@ -1,53 +1,57 @@ 'use strict'; const buildType = process.config.target_defaults.default_configuration; -const binding = require(`./build/${buildType}/binding.node`); const assert = require('assert'); const testUtil = require('./testUtil'); -testUtil.runGCTests([ - 'Internal ArrayBuffer', - () => { - const test = binding.arraybuffer.createBuffer(); - binding.arraybuffer.checkBuffer(test); - assert.ok(test instanceof ArrayBuffer); +test(require(`./build/${buildType}/binding.node`)); +test(require(`./build/${buildType}/binding_noexcept.node`)); - const test2 = test.slice(0); - binding.arraybuffer.checkBuffer(test2); - }, +function test(binding) { + testUtil.runGCTests([ + 'Internal ArrayBuffer', + () => { + const test = binding.arraybuffer.createBuffer(); + binding.arraybuffer.checkBuffer(test); + assert.ok(test instanceof ArrayBuffer); - 'External ArrayBuffer', - () => { - const test = binding.arraybuffer.createExternalBuffer(); - binding.arraybuffer.checkBuffer(test); - assert.ok(test instanceof ArrayBuffer); - assert.strictEqual(0, binding.arraybuffer.getFinalizeCount()); - }, - () => { - global.gc(); - assert.strictEqual(0, binding.arraybuffer.getFinalizeCount()); - }, + const test2 = test.slice(0); + binding.arraybuffer.checkBuffer(test2); + }, - 'External ArrayBuffer with finalizer', - () => { - const test = binding.arraybuffer.createExternalBufferWithFinalize(); - binding.arraybuffer.checkBuffer(test); - assert.ok(test instanceof ArrayBuffer); - assert.strictEqual(0, binding.arraybuffer.getFinalizeCount()); - }, - () => { - global.gc(); - assert.strictEqual(1, binding.arraybuffer.getFinalizeCount()); - }, + 'External ArrayBuffer', + () => { + const test = binding.arraybuffer.createExternalBuffer(); + binding.arraybuffer.checkBuffer(test); + assert.ok(test instanceof ArrayBuffer); + assert.strictEqual(0, binding.arraybuffer.getFinalizeCount()); + }, + () => { + global.gc(); + assert.strictEqual(0, binding.arraybuffer.getFinalizeCount()); + }, - 'External ArrayBuffer with finalizer hint', - () => { - const test = binding.arraybuffer.createExternalBufferWithFinalizeHint(); - binding.arraybuffer.checkBuffer(test); - assert.ok(test instanceof ArrayBuffer); - assert.strictEqual(0, binding.arraybuffer.getFinalizeCount()); - }, - () => { - global.gc(); - assert.strictEqual(1, binding.arraybuffer.getFinalizeCount()); - }, -]); + 'External ArrayBuffer with finalizer', + () => { + const test = binding.arraybuffer.createExternalBufferWithFinalize(); + binding.arraybuffer.checkBuffer(test); + assert.ok(test instanceof ArrayBuffer); + assert.strictEqual(0, binding.arraybuffer.getFinalizeCount()); + }, + () => { + global.gc(); + assert.strictEqual(1, binding.arraybuffer.getFinalizeCount()); + }, + + 'External ArrayBuffer with finalizer hint', + () => { + const test = binding.arraybuffer.createExternalBufferWithFinalizeHint(); + binding.arraybuffer.checkBuffer(test); + assert.ok(test instanceof ArrayBuffer); + assert.strictEqual(0, binding.arraybuffer.getFinalizeCount()); + }, + () => { + global.gc(); + assert.strictEqual(1, binding.arraybuffer.getFinalizeCount()); + }, + ]); +} diff --git a/test/asyncworker.js b/test/asyncworker.js index e43dcc4d0..92d2458aa 100644 --- a/test/asyncworker.js +++ b/test/asyncworker.js @@ -1,25 +1,21 @@ 'use strict'; const buildType = process.config.target_defaults.default_configuration; -const binding = require(`./build/${buildType}/binding.node`); const assert = require('assert'); -// Use setTimeout() when asserting after async callbacks because -// unhandled JS exceptions in async callbacks are currently ignored. -// See the TODO comment in AsyncWorker::OnWorkComplete(). +test(require(`./build/${buildType}/binding.node`)); +test(require(`./build/${buildType}/binding_noexcept.node`)); -binding.asyncworker.doWork(true, function (e) { - setTimeout(() => { +function test(binding) { + binding.asyncworker.doWork(true, function (e) { assert.strictEqual(typeof e, 'undefined'); assert.strictEqual(typeof this, 'object'); assert.strictEqual(this.data, 'test data'); - }); -}, 'test data'); + }, 'test data'); -binding.asyncworker.doWork(false, function (e) { - setTimeout(() => { + binding.asyncworker.doWork(false, function (e) { assert.ok(e instanceof Error); assert.strictEqual(e.message, 'test error'); assert.strictEqual(typeof this, 'object'); assert.strictEqual(this.data, 'test data'); - }); -}, 'test data'); + }, 'test data'); +} diff --git a/test/binding.gyp b/test/binding.gyp index 022b0d80c..d39e4bdfe 100644 --- a/test/binding.gyp +++ b/test/binding.gyp @@ -1,8 +1,6 @@ { - 'targets': [ - { - 'target_name': 'binding', - 'sources': [ + 'target_defaults': { + 'sources': [ 'arraybuffer.cc', 'asyncworker.cc', 'binding.cc', @@ -16,16 +14,35 @@ ], 'include_dirs': [" buffer = Buffer::New(info.Env(), testLength); if (buffer.Length() != testLength) { - throw Error::New(info.Env(), "Incorrect buffer length."); + Error::New(info.Env(), "Incorrect buffer length.").ThrowAsJavaScriptException(); + return Value(); } InitData(buffer.Data(), testLength); @@ -45,11 +46,13 @@ Value CreateExternalBuffer(const CallbackInfo& info) { testLength); if (buffer.Length() != testLength) { - throw Error::New(info.Env(), "Incorrect buffer length."); + Error::New(info.Env(), "Incorrect buffer length.").ThrowAsJavaScriptException(); + return Value(); } if (buffer.Data() != testData) { - throw Error::New(info.Env(), "Incorrect buffer data."); + Error::New(info.Env(), "Incorrect buffer data.").ThrowAsJavaScriptException(); + return Value(); } InitData(testData, testLength); @@ -71,11 +74,13 @@ Value CreateExternalBufferWithFinalize(const CallbackInfo& info) { }); if (buffer.Length() != testLength) { - throw Error::New(info.Env(), "Incorrect buffer length."); + Error::New(info.Env(), "Incorrect buffer length.").ThrowAsJavaScriptException(); + return Value(); } if (buffer.Data() != data) { - throw Error::New(info.Env(), "Incorrect buffer data."); + Error::New(info.Env(), "Incorrect buffer data.").ThrowAsJavaScriptException(); + return Value(); } InitData(data, testLength); @@ -99,11 +104,13 @@ Value CreateExternalBufferWithFinalizeHint(const CallbackInfo& info) { hint); if (buffer.Length() != testLength) { - throw Error::New(info.Env(), "Incorrect buffer length."); + Error::New(info.Env(), "Incorrect buffer length.").ThrowAsJavaScriptException(); + return Value(); } if (buffer.Data() != data) { - throw Error::New(info.Env(), "Incorrect buffer data."); + Error::New(info.Env(), "Incorrect buffer data.").ThrowAsJavaScriptException(); + return Value(); } InitData(data, testLength); @@ -117,15 +124,18 @@ Value CreateBufferCopy(const CallbackInfo& info) { info.Env(), testData, testLength); if (buffer.Length() != testLength) { - throw Error::New(info.Env(), "Incorrect buffer length."); + Error::New(info.Env(), "Incorrect buffer length.").ThrowAsJavaScriptException(); + return Value(); } if (buffer.Data() == testData) { - throw Error::New(info.Env(), "Copy should have different memory."); + Error::New(info.Env(), "Copy should have different memory.").ThrowAsJavaScriptException(); + return Value(); } if (!VerifyData(buffer.Data(), buffer.Length())) { - throw Error::New(info.Env(), "Copy data is incorrect."); + Error::New(info.Env(), "Copy data is incorrect.").ThrowAsJavaScriptException(); + return Value(); } return buffer; @@ -133,17 +143,20 @@ Value CreateBufferCopy(const CallbackInfo& info) { void CheckBuffer(const CallbackInfo& info) { if (!info[0].IsBuffer()) { - throw Error::New(info.Env(), "A buffer was expected."); + Error::New(info.Env(), "A buffer was expected.").ThrowAsJavaScriptException(); + return; } Buffer buffer = info[0].As>(); if (buffer.Length() != testLength) { - throw Error::New(info.Env(), "Incorrect buffer length."); + Error::New(info.Env(), "Incorrect buffer length.").ThrowAsJavaScriptException(); + return; } if (!VerifyData(buffer.Data(), testLength)) { - throw Error::New(info.Env(), "Incorrect buffer data."); + Error::New(info.Env(), "Incorrect buffer data.").ThrowAsJavaScriptException(); + return; } } diff --git a/test/buffer.js b/test/buffer.js index afdf67635..3e24e9d71 100644 --- a/test/buffer.js +++ b/test/buffer.js @@ -1,61 +1,65 @@ 'use strict'; const buildType = process.config.target_defaults.default_configuration; -const binding = require(`./build/${buildType}/binding.node`); const assert = require('assert'); const testUtil = require('./testUtil'); -testUtil.runGCTests([ - 'Internal Buffer', - () => { - const test = binding.buffer.createBuffer(); - binding.buffer.checkBuffer(test); - assert.ok(test instanceof Buffer); +test(require(`./build/${buildType}/binding.node`)); +test(require(`./build/${buildType}/binding_noexcept.node`)); - const test2 = Buffer.alloc(test.length); - test.copy(test2); - binding.buffer.checkBuffer(test2); - }, +function test(binding) { + testUtil.runGCTests([ + 'Internal Buffer', + () => { + const test = binding.buffer.createBuffer(); + binding.buffer.checkBuffer(test); + assert.ok(test instanceof Buffer); - 'Buffer copy', - () => { - const test = binding.buffer.createBufferCopy(); - binding.buffer.checkBuffer(test); - assert.ok(test instanceof Buffer); - }, + const test2 = Buffer.alloc(test.length); + test.copy(test2); + binding.buffer.checkBuffer(test2); + }, - 'External Buffer', - () => { - const test = binding.buffer.createExternalBuffer(); - binding.buffer.checkBuffer(test); - assert.ok(test instanceof Buffer); - assert.strictEqual(0, binding.buffer.getFinalizeCount()); - }, - () => { - global.gc(); - assert.strictEqual(0, binding.buffer.getFinalizeCount()); - }, + 'Buffer copy', + () => { + const test = binding.buffer.createBufferCopy(); + binding.buffer.checkBuffer(test); + assert.ok(test instanceof Buffer); + }, - 'External Buffer with finalizer', - () => { - const test = binding.buffer.createExternalBufferWithFinalize(); - binding.buffer.checkBuffer(test); - assert.ok(test instanceof Buffer); - assert.strictEqual(0, binding.buffer.getFinalizeCount()); - }, - () => { - global.gc(); - assert.strictEqual(1, binding.buffer.getFinalizeCount()); - }, + 'External Buffer', + () => { + const test = binding.buffer.createExternalBuffer(); + binding.buffer.checkBuffer(test); + assert.ok(test instanceof Buffer); + assert.strictEqual(0, binding.buffer.getFinalizeCount()); + }, + () => { + global.gc(); + assert.strictEqual(0, binding.buffer.getFinalizeCount()); + }, - 'External Buffer with finalizer hint', - () => { - const test = binding.buffer.createExternalBufferWithFinalizeHint(); + 'External Buffer with finalizer', + () => { + const test = binding.buffer.createExternalBufferWithFinalize(); binding.buffer.checkBuffer(test); assert.ok(test instanceof Buffer); assert.strictEqual(0, binding.buffer.getFinalizeCount()); - }, - () => { - global.gc(); - assert.strictEqual(1, binding.buffer.getFinalizeCount()); - }, -]); + }, + () => { + global.gc(); + assert.strictEqual(1, binding.buffer.getFinalizeCount()); + }, + + 'External Buffer with finalizer hint', + () => { + const test = binding.buffer.createExternalBufferWithFinalizeHint(); + binding.buffer.checkBuffer(test); + assert.ok(test instanceof Buffer); + assert.strictEqual(0, binding.buffer.getFinalizeCount()); + }, + () => { + global.gc(); + assert.strictEqual(1, binding.buffer.getFinalizeCount()); + }, + ]); +} diff --git a/test/error.cc b/test/error.cc index a7dcefe4b..ffe1331bb 100644 --- a/test/error.cc +++ b/test/error.cc @@ -4,11 +4,18 @@ using namespace Napi; namespace { +void DoNotCatch(const CallbackInfo& info) { + Function thrower = info[0].As(); + thrower({}); +} + void ThrowApiError(const CallbackInfo& info) { // Attempting to call an empty function value will throw an API error. Function(info.Env(), nullptr).Call({}); } +#ifdef NAPI_CPP_EXCEPTIONS + void ThrowJSError(const CallbackInfo& info) { std::string message = info[0].As().Utf8Value(); throw Error::New(info.Env(), message); @@ -29,7 +36,7 @@ Value CatchError(const CallbackInfo& info) { try { thrower({}); } catch (const Error& e) { - return e.Value(); + return e.Value(); } return info.Env().Null(); } @@ -39,24 +46,19 @@ Value CatchErrorMessage(const CallbackInfo& info) { try { thrower({}); } catch (const Error& e) { - std::string message = e.Message(); - return String::New(info.Env(), message); + std::string message = e.Message(); + return String::New(info.Env(), message); } return info.Env().Null(); } -void DoNotCatch(const CallbackInfo& info) { - Function thrower = info[0].As(); - thrower({}); -} - void CatchAndRethrowError(const CallbackInfo& info) { Function thrower = info[0].As(); try { thrower({}); } catch (Error& e) { - e.Set("caught", Boolean::New(info.Env(), true)); - throw; + e.Set("caught", Boolean::New(info.Env(), true)); + throw; } } @@ -77,6 +79,81 @@ void CatchAndRethrowErrorThatEscapesScope(const CallbackInfo& info) { } } +#else // NAPI_CPP_EXCEPTIONS + +void ThrowJSError(const CallbackInfo& info) { + std::string message = info[0].As().Utf8Value(); + Error::New(info.Env(), message).ThrowAsJavaScriptException(); +} + +void ThrowTypeError(const CallbackInfo& info) { + std::string message = info[0].As().Utf8Value(); + TypeError::New(info.Env(), message).ThrowAsJavaScriptException(); +} + +void ThrowRangeError(const CallbackInfo& info) { + std::string message = info[0].As().Utf8Value(); + RangeError::New(info.Env(), message).ThrowAsJavaScriptException(); +} + +Value CatchError(const CallbackInfo& info) { + Function thrower = info[0].As(); + thrower({}); + + Env env = info.Env(); + if (env.IsExceptionPending()) { + Error e = env.GetAndClearPendingException(); + return e.Value(); + } + return info.Env().Null(); +} + +Value CatchErrorMessage(const CallbackInfo& info) { + Function thrower = info[0].As(); + thrower({}); + + Env env = info.Env(); + if (env.IsExceptionPending()) { + Error e = env.GetAndClearPendingException(); + std::string message = e.Message(); + return String::New(env, message); + } + return info.Env().Null(); +} + +void CatchAndRethrowError(const CallbackInfo& info) { + Function thrower = info[0].As(); + thrower({}); + + Env env = info.Env(); + if (env.IsExceptionPending()) { + Error e = env.GetAndClearPendingException(); + e.Set("caught", Boolean::New(info.Env(), true)); + e.ThrowAsJavaScriptException(); + } +} + +void ThrowErrorThatEscapesScope(const CallbackInfo& info) { + HandleScope scope(info.Env()); + + std::string message = info[0].As().Utf8Value(); + Error::New(info.Env(), message).ThrowAsJavaScriptException(); +} + +void CatchAndRethrowErrorThatEscapesScope(const CallbackInfo& info) { + HandleScope scope(info.Env()); + ThrowErrorThatEscapesScope(info); + + Env env = info.Env(); + if (env.IsExceptionPending()) { + Error e = env.GetAndClearPendingException(); + e.Set("caught", Boolean::New(info.Env(), true)); + e.ThrowAsJavaScriptException(); + } +} + +#endif // NAPI_CPP_EXCEPTIONS + } // end anonymous namespace Object InitError(Env env) { diff --git a/test/error.js b/test/error.js index f82793f30..5b62fa0de 100644 --- a/test/error.js +++ b/test/error.js @@ -1,55 +1,59 @@ 'use strict'; const buildType = process.config.target_defaults.default_configuration; -const binding = require(`./build/${buildType}/binding.node`); const assert = require('assert'); -assert.throws(() => binding.error.throwApiError('test'), err => { - return err instanceof Error && err.message.includes('Invalid'); -}); - -assert.throws(() => binding.error.throwJSError('test'), err => { - return err instanceof Error && err.message === 'test'; -}); - -assert.throws(() => binding.error.throwTypeError('test'), err => { - return err instanceof TypeError && err.message === 'test'; -}); - -assert.throws(() => binding.error.throwRangeError('test'), err => { - return err instanceof RangeError && err.message === 'test'; -}); - -assert.throws( - () => binding.error.doNotCatch( - () => { - throw new TypeError('test'); - }), - err => { - return err instanceof TypeError && err.message === 'test' && !err.caught; +test(require(`./build/${buildType}/binding.node`)); +test(require(`./build/${buildType}/binding_noexcept.node`)); + +function test(binding) { + assert.throws(() => binding.error.throwApiError('test'), err => { + return err instanceof Error && err.message.includes('Invalid'); }); -assert.throws( - () => binding.error.catchAndRethrowError( - () => { - throw new TypeError('test'); - }), - err => { - return err instanceof TypeError && err.message === 'test' && err.caught; + assert.throws(() => binding.error.throwJSError('test'), err => { + return err instanceof Error && err.message === 'test'; }); -const err = binding.error.catchError( - () => { throw new TypeError('test'); }); -assert(err instanceof TypeError); -assert.strictEqual(err.message, 'test'); + assert.throws(() => binding.error.throwTypeError('test'), err => { + return err instanceof TypeError && err.message === 'test'; + }); -const msg = binding.error.catchErrorMessage( - () => { throw new TypeError('test'); }); -assert.strictEqual(msg, 'test'); + assert.throws(() => binding.error.throwRangeError('test'), err => { + return err instanceof RangeError && err.message === 'test'; + }); -assert.throws(() => binding.error.throwErrorThatEscapesScope('test'), err => { - return err instanceof Error && err.message === 'test'; -}); + assert.throws( + () => binding.error.doNotCatch( + () => { + throw new TypeError('test'); + }), + err => { + return err instanceof TypeError && err.message === 'test' && !err.caught; + }); + + assert.throws( + () => binding.error.catchAndRethrowError( + () => { + throw new TypeError('test'); + }), + err => { + return err instanceof TypeError && err.message === 'test' && err.caught; + }); + + const err = binding.error.catchError( + () => { throw new TypeError('test'); }); + assert(err instanceof TypeError); + assert.strictEqual(err.message, 'test'); + + const msg = binding.error.catchErrorMessage( + () => { throw new TypeError('test'); }); + assert.strictEqual(msg, 'test'); + + assert.throws(() => binding.error.throwErrorThatEscapesScope('test'), err => { + return err instanceof Error && err.message === 'test'; + }); -assert.throws(() => binding.error.catchAndRethrowErrorThatEscapesScope('test'), err => { - return err instanceof Error && err.message === 'test' && err.caught; -}); + assert.throws(() => binding.error.catchAndRethrowErrorThatEscapesScope('test'), err => { + return err instanceof Error && err.message === 'test' && err.caught; + }); +} diff --git a/test/external.cc b/test/external.cc index 28856dc23..ad4781c18 100644 --- a/test/external.cc +++ b/test/external.cc @@ -35,13 +35,15 @@ Value CreateExternalWithFinalizeHint(const CallbackInfo& info) { void CheckExternal(const CallbackInfo& info) { Value arg = info[0]; if (arg.Type() != napi_external) { - throw Error::New(info.Env(), "An external argument was expected."); + Error::New(info.Env(), "An external argument was expected.").ThrowAsJavaScriptException(); + return; } External external = arg.As>(); int* externalData = external.Data(); if (externalData == nullptr || *externalData != 1) { - throw Error::New(info.Env(), "An external value of 1 was expected."); + Error::New(info.Env(), "An external value of 1 was expected.").ThrowAsJavaScriptException(); + return; } } diff --git a/test/external.js b/test/external.js index d702a6a96..e2067006a 100644 --- a/test/external.js +++ b/test/external.js @@ -1,40 +1,44 @@ 'use strict'; const buildType = process.config.target_defaults.default_configuration; -const binding = require(`./build/${buildType}/binding.node`); const assert = require('assert'); const testUtil = require('./testUtil'); -testUtil.runGCTests([ - 'External without finalizer', - () => { - const test = binding.external.createExternal(); - assert.strictEqual(typeof test, 'object'); - binding.external.checkExternal(test); - assert.strictEqual(0, binding.external.getFinalizeCount()); - }, - () => { - assert.strictEqual(0, binding.external.getFinalizeCount()); - }, +test(require(`./build/${buildType}/binding.node`)); +test(require(`./build/${buildType}/binding_noexcept.node`)); - 'External with finalizer', - () => { - const test = binding.external.createExternalWithFinalize(); - assert.strictEqual(typeof test, 'object'); - binding.external.checkExternal(test); - assert.strictEqual(0, binding.external.getFinalizeCount()); - }, - () => { - assert.strictEqual(1, binding.external.getFinalizeCount()); - }, +function test(binding) { + testUtil.runGCTests([ + 'External without finalizer', + () => { + const test = binding.external.createExternal(); + assert.strictEqual(typeof test, 'object'); + binding.external.checkExternal(test); + assert.strictEqual(0, binding.external.getFinalizeCount()); + }, + () => { + assert.strictEqual(0, binding.external.getFinalizeCount()); + }, - 'External with finalizer hint', - () => { - const test = binding.external.createExternalWithFinalizeHint(); - assert.strictEqual(typeof test, 'object'); - binding.external.checkExternal(test); - assert.strictEqual(0, binding.external.getFinalizeCount()); - }, - () => { - assert.strictEqual(1, binding.external.getFinalizeCount()); - }, -]); + 'External with finalizer', + () => { + const test = binding.external.createExternalWithFinalize(); + assert.strictEqual(typeof test, 'object'); + binding.external.checkExternal(test); + assert.strictEqual(0, binding.external.getFinalizeCount()); + }, + () => { + assert.strictEqual(1, binding.external.getFinalizeCount()); + }, + + 'External with finalizer hint', + () => { + const test = binding.external.createExternalWithFinalizeHint(); + assert.strictEqual(typeof test, 'object'); + binding.external.checkExternal(test); + assert.strictEqual(0, binding.external.getFinalizeCount()); + }, + () => { + assert.strictEqual(1, binding.external.getFinalizeCount()); + }, + ]); +} diff --git a/test/function.cc b/test/function.cc index ad4a2fb42..0df54b7a8 100644 --- a/test/function.cc +++ b/test/function.cc @@ -76,6 +76,11 @@ Value CallWithReceiverAndVector(const CallbackInfo& info) { return func.Call(receiver, args); } +Value CallWithInvalidReceiver(const CallbackInfo& info) { + Function func = info[0].As(); + return func.Call(Value(), {}); +} + Value CallConstructorWithArgs(const CallbackInfo& info) { Function func = info[0].As(); return func.New({ info[1], info[2], info[3] }); @@ -105,6 +110,7 @@ Object InitFunction(Env env) { exports["callWithVector"] = Function::New(env, CallWithVector); exports["callWithReceiverAndArgs"] = Function::New(env, CallWithReceiverAndArgs); exports["callWithReceiverAndVector"] = Function::New(env, CallWithReceiverAndVector); + exports["callWithInvalidReceiver"] = Function::New(env, CallWithInvalidReceiver); exports["callConstructorWithArgs"] = Function::New(env, CallConstructorWithArgs); exports["callConstructorWithVector"] = Function::New(env, CallConstructorWithVector); return exports; diff --git a/test/function.js b/test/function.js index 76c2fa329..ef20d012f 100644 --- a/test/function.js +++ b/test/function.js @@ -1,61 +1,69 @@ 'use strict'; const buildType = process.config.target_defaults.default_configuration; -const binding = require(`./build/${buildType}/binding.node`); const assert = require('assert'); -let obj = {}; -assert.deepStrictEqual(binding.function.voidCallback(obj), undefined); -assert.deepStrictEqual(obj, { "foo": "bar" }); +test(require(`./build/${buildType}/binding.node`)); +test(require(`./build/${buildType}/binding_noexcept.node`)); -assert.deepStrictEqual(binding.function.valueCallback(), { "foo": "bar" }); +function test(binding) { + let obj = {}; + assert.deepStrictEqual(binding.function.voidCallback(obj), undefined); + assert.deepStrictEqual(obj, { "foo": "bar" }); -let args = null; -let ret = null; -let receiver = null; -function testFunction() { - receiver = this; - args = [].slice.call(arguments); - return ret; -} -function testConstructor() { - args = [].slice.call(arguments); -} + assert.deepStrictEqual(binding.function.valueCallback(), { "foo": "bar" }); + + let args = null; + let ret = null; + let receiver = null; + function testFunction() { + receiver = this; + args = [].slice.call(arguments); + return ret; + } + function testConstructor() { + args = [].slice.call(arguments); + } -ret = 4; -assert.equal(binding.function.callWithArgs(testFunction, 1, 2, 3), 4); -assert.strictEqual(receiver, undefined); -assert.deepStrictEqual(args, [ 1, 2, 3 ]); + ret = 4; + assert.equal(binding.function.callWithArgs(testFunction, 1, 2, 3), 4); + assert.strictEqual(receiver, undefined); + assert.deepStrictEqual(args, [ 1, 2, 3 ]); -ret = 5; -assert.equal(binding.function.callWithVector(testFunction, 2, 3, 4), 5); -assert.strictEqual(receiver, undefined); -assert.deepStrictEqual(args, [ 2, 3, 4 ]); + ret = 5; + assert.equal(binding.function.callWithVector(testFunction, 2, 3, 4), 5); + assert.strictEqual(receiver, undefined); + assert.deepStrictEqual(args, [ 2, 3, 4 ]); -ret = 6; -assert.equal(binding.function.callWithReceiverAndArgs(testFunction, obj, 3, 4, 5), 6); -assert.deepStrictEqual(receiver, obj); -assert.deepStrictEqual(args, [ 3, 4, 5 ]); + ret = 6; + assert.equal(binding.function.callWithReceiverAndArgs(testFunction, obj, 3, 4, 5), 6); + assert.deepStrictEqual(receiver, obj); + assert.deepStrictEqual(args, [ 3, 4, 5 ]); -ret = 7; -assert.equal(binding.function.callWithReceiverAndVector(testFunction, obj, 4, 5, 6), 7); -assert.deepStrictEqual(receiver, obj); -assert.deepStrictEqual(args, [ 4, 5, 6 ]); + ret = 7; + assert.equal(binding.function.callWithReceiverAndVector(testFunction, obj, 4, 5, 6), 7); + assert.deepStrictEqual(receiver, obj); + assert.deepStrictEqual(args, [ 4, 5, 6 ]); -obj = binding.function.callConstructorWithArgs(testConstructor, 5, 6, 7); -assert(obj instanceof testConstructor); -assert.deepStrictEqual(args, [ 5, 6, 7 ]); + assert.throws(() => { + binding.function.callWithInvalidReceiver(); + }, /Invalid pointer/); -obj = binding.function.callConstructorWithVector(testConstructor, 6, 7, 8); -assert(obj instanceof testConstructor); -assert.deepStrictEqual(args, [ 6, 7, 8 ]); + obj = binding.function.callConstructorWithArgs(testConstructor, 5, 6, 7); + assert(obj instanceof testConstructor); + assert.deepStrictEqual(args, [ 5, 6, 7 ]); -obj = {}; -assert.deepStrictEqual(binding.function.voidCallbackWithData(obj), undefined); -assert.deepStrictEqual(obj, { "foo": "bar", "data": 1 }); + obj = binding.function.callConstructorWithVector(testConstructor, 6, 7, 8); + assert(obj instanceof testConstructor); + assert.deepStrictEqual(args, [ 6, 7, 8 ]); -assert.deepStrictEqual(binding.function.valueCallbackWithData(), { "foo": "bar", "data": 1 }); + obj = {}; + assert.deepStrictEqual(binding.function.voidCallbackWithData(obj), undefined); + assert.deepStrictEqual(obj, { "foo": "bar", "data": 1 }); -assert.equal(binding.function.voidCallback.name, 'voidCallback'); -assert.equal(binding.function.valueCallback.name, 'valueCallback'); + assert.deepStrictEqual(binding.function.valueCallbackWithData(), { "foo": "bar", "data": 1 }); -// TODO: Function::MakeCallback tests + assert.equal(binding.function.voidCallback.name, 'voidCallback'); + assert.equal(binding.function.valueCallback.name, 'valueCallback'); + + // TODO: Function::MakeCallback tests +} diff --git a/test/name.cc b/test/name.cc index e264fd4a8..4474df685 100644 --- a/test/name.cc +++ b/test/name.cc @@ -14,7 +14,8 @@ Value EchoString(const CallbackInfo& info) { } else if (encoding.Utf8Value() == "utf16") { return String::New(info.Env(), value.Utf16Value().c_str()); } else { - throw Error::New(info.Env(), "Invalid encoding."); + Error::New(info.Env(), "Invalid encoding.").ThrowAsJavaScriptException(); + return Value(); } } @@ -35,7 +36,8 @@ Value CreateString(const CallbackInfo& info) { return String::New(info.Env(), testValueUtf16, length.Uint32Value()); } } else { - throw Error::New(info.Env(), "Invalid encoding."); + Error::New(info.Env(), "Invalid encoding.").ThrowAsJavaScriptException(); + return Value(); } } @@ -61,7 +63,8 @@ Value CheckString(const CallbackInfo& info) { std::u16string stringValue = value; return Boolean::New(info.Env(), stringValue == testValue); } else { - throw Error::New(info.Env(), "Invalid encoding."); + Error::New(info.Env(), "Invalid encoding.").ThrowAsJavaScriptException(); + return Value(); } } diff --git a/test/name.js b/test/name.js index 38aeddffa..4e9659312 100644 --- a/test/name.js +++ b/test/name.js @@ -1,51 +1,55 @@ 'use strict'; const buildType = process.config.target_defaults.default_configuration; -const binding = require(`./build/${buildType}/binding.node`); const assert = require('assert'); -const expected = '123456789'; - -assert.ok(binding.name.checkString(expected, 'utf8')); -assert.ok(binding.name.checkString(expected, 'utf16')); -assert.ok(binding.name.checkString(expected.substr(0, 3), 'utf8', 3)); -assert.ok(binding.name.checkString(expected.substr(0, 3), 'utf16', 3)); - -const str1 = binding.name.createString('utf8'); -assert.strictEqual(str1, expected); -assert.ok(binding.name.checkString(str1, 'utf8')); -assert.ok(binding.name.checkString(str1, 'utf16')); - -const substr1 = binding.name.createString('utf8', 3); -assert.strictEqual(substr1, expected.substr(0, 3)); -assert.ok(binding.name.checkString(substr1, 'utf8', 3)); -assert.ok(binding.name.checkString(substr1, 'utf16', 3)); - -const str2 = binding.name.createString('utf16'); -assert.strictEqual(str1, expected); -assert.ok(binding.name.checkString(str2, 'utf8')); -assert.ok(binding.name.checkString(str2, 'utf16')); - -const substr2 = binding.name.createString('utf16', 3); -assert.strictEqual(substr1, expected.substr(0, 3)); -assert.ok(binding.name.checkString(substr2, 'utf8', 3)); -assert.ok(binding.name.checkString(substr2, 'utf16', 3)); - -assert.ok(binding.name.checkSymbol(Symbol())); -assert.ok(binding.name.checkSymbol(Symbol('test'))); - -const sym1 = binding.name.createSymbol(); -assert.strictEqual(typeof sym1, 'symbol'); -assert.ok(binding.name.checkSymbol(sym1)); - -const sym2 = binding.name.createSymbol('test'); -assert.strictEqual(typeof sym2, 'symbol'); -assert.ok(binding.name.checkSymbol(sym1)); - -// Check for off-by-one errors which might only appear for strings of certain sizes, -// due to how std::string increments its capacity in chunks. -const longString = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; -for (let i = 10; i <= longString.length; i++) { - const str = longString.substr(0, i); - assert.strictEqual(binding.name.echoString(str, 'utf8'), str); - assert.strictEqual(binding.name.echoString(str, 'utf16'), str); +test(require(`./build/${buildType}/binding.node`)); +test(require(`./build/${buildType}/binding_noexcept.node`)); + +function test(binding) { + const expected = '123456789'; + + assert.ok(binding.name.checkString(expected, 'utf8')); + assert.ok(binding.name.checkString(expected, 'utf16')); + assert.ok(binding.name.checkString(expected.substr(0, 3), 'utf8', 3)); + assert.ok(binding.name.checkString(expected.substr(0, 3), 'utf16', 3)); + + const str1 = binding.name.createString('utf8'); + assert.strictEqual(str1, expected); + assert.ok(binding.name.checkString(str1, 'utf8')); + assert.ok(binding.name.checkString(str1, 'utf16')); + + const substr1 = binding.name.createString('utf8', 3); + assert.strictEqual(substr1, expected.substr(0, 3)); + assert.ok(binding.name.checkString(substr1, 'utf8', 3)); + assert.ok(binding.name.checkString(substr1, 'utf16', 3)); + + const str2 = binding.name.createString('utf16'); + assert.strictEqual(str1, expected); + assert.ok(binding.name.checkString(str2, 'utf8')); + assert.ok(binding.name.checkString(str2, 'utf16')); + + const substr2 = binding.name.createString('utf16', 3); + assert.strictEqual(substr1, expected.substr(0, 3)); + assert.ok(binding.name.checkString(substr2, 'utf8', 3)); + assert.ok(binding.name.checkString(substr2, 'utf16', 3)); + + assert.ok(binding.name.checkSymbol(Symbol())); + assert.ok(binding.name.checkSymbol(Symbol('test'))); + + const sym1 = binding.name.createSymbol(); + assert.strictEqual(typeof sym1, 'symbol'); + assert.ok(binding.name.checkSymbol(sym1)); + + const sym2 = binding.name.createSymbol('test'); + assert.strictEqual(typeof sym2, 'symbol'); + assert.ok(binding.name.checkSymbol(sym1)); + + // Check for off-by-one errors which might only appear for strings of certain sizes, + // due to how std::string increments its capacity in chunks. + const longString = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; + for (let i = 10; i <= longString.length; i++) { + const str = longString.substr(0, i); + assert.strictEqual(binding.name.echoString(str, 'utf8'), str); + assert.strictEqual(binding.name.echoString(str, 'utf16'), str); + } } diff --git a/test/object.cc b/test/object.cc index 2d13de4ba..e51064db8 100644 --- a/test/object.cc +++ b/test/object.cc @@ -70,11 +70,27 @@ void DefineValueProperty(const CallbackInfo& info) { obj.DefineProperty(PropertyDescriptor::Value(name, value)); } +Value GetProperty(const CallbackInfo& info) { + Object obj = info[0].As(); + Name name = info[1].As(); + Value value = obj.Get(name); + return value; +} + +void SetProperty(const CallbackInfo& info) { + Object obj = info[0].As(); + Name name = info[1].As(); + Value value = info[2]; + obj.Set(name, value); +} + Object InitObject(Env env) { Object exports = Object::New(env); exports["defineProperties"] = Function::New(env, DefineProperties); exports["defineValueProperty"] = Function::New(env, DefineValueProperty); + exports["getProperty"] = Function::New(env, GetProperty); + exports["setProperty"] = Function::New(env, SetProperty); return exports; } diff --git a/test/object.js b/test/object.js index 1e4f6d5d3..855509db5 100644 --- a/test/object.js +++ b/test/object.js @@ -1,69 +1,91 @@ 'use strict'; const buildType = process.config.target_defaults.default_configuration; -const binding = require(`./build/${buildType}/binding.node`); const assert = require('assert'); -function assertPropertyIs(obj, key, attribute) { - const propDesc = Object.getOwnPropertyDescriptor(obj, key); - assert.ok(propDesc); - assert.ok(propDesc[attribute]); -} +test(require(`./build/${buildType}/binding.node`)); +test(require(`./build/${buildType}/binding_noexcept.node`)); -function assertPropertyIsNot(obj, key, attribute) { - const propDesc = Object.getOwnPropertyDescriptor(obj, key); - assert.ok(propDesc); - assert.ok(!propDesc[attribute]); -} +function test(binding) { + function assertPropertyIs(obj, key, attribute) { + const propDesc = Object.getOwnPropertyDescriptor(obj, key); + assert.ok(propDesc); + assert.ok(propDesc[attribute]); + } -function testDefineProperties(nameType) { - const obj = {}; - binding.object.defineProperties(obj, nameType); - - assertPropertyIsNot(obj, 'readonlyAccessor', 'enumerable'); - assertPropertyIsNot(obj, 'readonlyAccessor', 'configurable'); - assert.strictEqual(obj.readonlyAccessor, true); - - assertPropertyIsNot(obj, 'readwriteAccessor', 'enumerable'); - assertPropertyIsNot(obj, 'readwriteAccessor', 'configurable'); - obj.readwriteAccessor = false; - assert.strictEqual(obj.readwriteAccessor, false); - obj.readwriteAccessor = true; - assert.strictEqual(obj.readwriteAccessor, true); - - assertPropertyIsNot(obj, 'readonlyValue', 'writable'); - assertPropertyIsNot(obj, 'readonlyValue', 'enumerable'); - assertPropertyIsNot(obj, 'readonlyValue', 'configurable'); - assert.strictEqual(obj.readonlyValue, true); - - assertPropertyIs(obj, 'readwriteValue', 'writable'); - assertPropertyIsNot(obj, 'readwriteValue', 'enumerable'); - assertPropertyIsNot(obj, 'readwriteValue', 'configurable'); - obj.readwriteValue = false; - assert.strictEqual(obj.readwriteValue, false); - obj.readwriteValue = true; - assert.strictEqual(obj.readwriteValue, true); - - assertPropertyIsNot(obj, 'enumerableValue', 'writable'); - assertPropertyIs(obj, 'enumerableValue', 'enumerable'); - assertPropertyIsNot(obj, 'enumerableValue', 'configurable'); - - assertPropertyIsNot(obj, 'configurableValue', 'writable'); - assertPropertyIsNot(obj, 'configurableValue', 'enumerable'); - assertPropertyIs(obj, 'configurableValue', 'configurable'); - - assertPropertyIsNot(obj, 'function', 'writable'); - assertPropertyIsNot(obj, 'function', 'enumerable'); - assertPropertyIsNot(obj, 'function', 'configurable'); - assert.strictEqual(obj.function(), true); -} + function assertPropertyIsNot(obj, key, attribute) { + const propDesc = Object.getOwnPropertyDescriptor(obj, key); + assert.ok(propDesc); + assert.ok(!propDesc[attribute]); + } + + function testDefineProperties(nameType) { + const obj = {}; + binding.object.defineProperties(obj, nameType); + + assertPropertyIsNot(obj, 'readonlyAccessor', 'enumerable'); + assertPropertyIsNot(obj, 'readonlyAccessor', 'configurable'); + assert.strictEqual(obj.readonlyAccessor, true); + + assertPropertyIsNot(obj, 'readwriteAccessor', 'enumerable'); + assertPropertyIsNot(obj, 'readwriteAccessor', 'configurable'); + obj.readwriteAccessor = false; + assert.strictEqual(obj.readwriteAccessor, false); + obj.readwriteAccessor = true; + assert.strictEqual(obj.readwriteAccessor, true); + + assertPropertyIsNot(obj, 'readonlyValue', 'writable'); + assertPropertyIsNot(obj, 'readonlyValue', 'enumerable'); + assertPropertyIsNot(obj, 'readonlyValue', 'configurable'); + assert.strictEqual(obj.readonlyValue, true); + + assertPropertyIs(obj, 'readwriteValue', 'writable'); + assertPropertyIsNot(obj, 'readwriteValue', 'enumerable'); + assertPropertyIsNot(obj, 'readwriteValue', 'configurable'); + obj.readwriteValue = false; + assert.strictEqual(obj.readwriteValue, false); + obj.readwriteValue = true; + assert.strictEqual(obj.readwriteValue, true); + + assertPropertyIsNot(obj, 'enumerableValue', 'writable'); + assertPropertyIs(obj, 'enumerableValue', 'enumerable'); + assertPropertyIsNot(obj, 'enumerableValue', 'configurable'); + + assertPropertyIsNot(obj, 'configurableValue', 'writable'); + assertPropertyIsNot(obj, 'configurableValue', 'enumerable'); + assertPropertyIs(obj, 'configurableValue', 'configurable'); + + assertPropertyIsNot(obj, 'function', 'writable'); + assertPropertyIsNot(obj, 'function', 'enumerable'); + assertPropertyIsNot(obj, 'function', 'configurable'); + assert.strictEqual(obj.function(), true); + } + + testDefineProperties('literal'); + testDefineProperties('string'); + testDefineProperties('value'); + + { + const obj = {}; + const testSym = Symbol(); + binding.object.defineValueProperty(obj, testSym, 1); + assert.strictEqual(obj[testSym], 1); + } + + { + const obj = { test: 1 }; + assert.strictEqual(binding.object.getProperty(obj, 'test'), 1); + } -testDefineProperties('literal'); -testDefineProperties('string'); -testDefineProperties('value'); + { + const obj = {}; + binding.object.setProperty(obj, 'test', 1); + assert.strictEqual(obj.test, 1); + } -{ - const obj = {}; - const testSym = Symbol(); - binding.object.defineValueProperty(obj, testSym, 1); - assert.strictEqual(obj[testSym], 1); + assert.throws(() => { + binding.object.getProperty(undefined, 'test'); + }, /object was expected/); + assert.throws(() => { + binding.object.setProperty(undefined, 'test', 1); + }, /object was expected/); } diff --git a/test/typedarray.cc b/test/typedarray.cc index ff1232cf1..f005a4013 100644 --- a/test/typedarray.cc +++ b/test/typedarray.cc @@ -38,10 +38,15 @@ Value CreateTypedArray(const CallbackInfo& info) { return buffer.IsUndefined() ? Float64Array::New(info.Env(), length) : Float64Array::New(info.Env(), length, buffer, bufferOffset); } else { - throw Error::New(info.Env(), "Invalid typed-array type."); + Error::New(info.Env(), "Invalid typed-array type.").ThrowAsJavaScriptException(); + return Value(); } } +Value CreateInvalidTypedArray(const CallbackInfo& info) { + return Int8Array::New(info.Env(), 1, ArrayBuffer(), 0); +} + Value GetTypedArrayType(const CallbackInfo& info) { TypedArray array = info[0].As(); switch (array.TypedArrayType()) { @@ -90,7 +95,9 @@ Value GetTypedArrayElement(const CallbackInfo& info) { return Number::New(info.Env(), array.As()[index]); case napi_float64_array: return Number::New(info.Env(), array.As()[index]); - default: throw Error::New(info.Env(), "Invalid typed-array type."); + default: + Error::New(info.Env(), "Invalid typed-array type.").ThrowAsJavaScriptException(); + return Value(); } } @@ -127,7 +134,7 @@ void SetTypedArrayElement(const CallbackInfo& info) { array.As()[index] = value.DoubleValue(); break; default: - throw Error::New(info.Env(), "Invalid typed-array type."); + Error::New(info.Env(), "Invalid typed-array type.").ThrowAsJavaScriptException(); } } @@ -137,6 +144,7 @@ Object InitTypedArray(Env env) { Object exports = Object::New(env); exports["createTypedArray"] = Function::New(env, CreateTypedArray); + exports["createInvalidTypedArray"] = Function::New(env, CreateInvalidTypedArray); exports["getTypedArrayType"] = Function::New(env, GetTypedArrayType); exports["getTypedArrayLength"] = Function::New(env, GetTypedArrayLength); exports["getTypedArrayBuffer"] = Function::New(env, GetTypedArrayBuffer); diff --git a/test/typedarray.js b/test/typedarray.js index 2addfeaa8..f5ffca200 100644 --- a/test/typedarray.js +++ b/test/typedarray.js @@ -1,22 +1,25 @@ 'use strict'; const buildType = process.config.target_defaults.default_configuration; -const binding = require(`./build/${buildType}/binding.node`); const assert = require('assert'); -const testData = [ - [ 'int8', Int8Array ], - [ 'uint8', Uint8Array ], - [ 'uint8_clamped', Uint8ClampedArray ], - [ 'int16', Int16Array ], - [ 'uint16', Uint16Array ], - [ 'int32', Int32Array ], - [ 'uint32', Uint32Array ], - [ 'float32', Float32Array ], - [ 'float64', Float64Array ], -]; - -testData.forEach(data => { - try { +test(require(`./build/${buildType}/binding.node`)); +test(require(`./build/${buildType}/binding_noexcept.node`)); + +function test(binding) { + const testData = [ + [ 'int8', Int8Array ], + [ 'uint8', Uint8Array ], + [ 'uint8_clamped', Uint8ClampedArray ], + [ 'int16', Int16Array ], + [ 'uint16', Uint16Array ], + [ 'int32', Int32Array ], + [ 'uint32', Uint32Array ], + [ 'float32', Float32Array ], + [ 'float64', Float64Array ], + ]; + + testData.forEach(data => { + try { const length = 4; const t = binding.typedarray.createTypedArray(data[0], length); assert.ok(t instanceof data[1]); @@ -31,14 +34,14 @@ testData.forEach(data => { const b = binding.typedarray.getTypedArrayBuffer(t); assert.ok(b instanceof ArrayBuffer); - } catch (e) { + } catch (e) { console.log(data); throw e; - } -}); + } + }); -testData.forEach(data => { - try { + testData.forEach(data => { + try { const length = 4; const offset = 8; const b = new ArrayBuffer(offset + 64 * 4); @@ -55,8 +58,13 @@ testData.forEach(data => { assert.strictEqual(t[3], 22); assert.strictEqual(binding.typedarray.getTypedArrayBuffer(t), b); - } catch (e) { + } catch (e) { console.log(data); throw e; - } -}); + } + }); + + assert.throws(() => { + binding.typedarray.createInvalidTypedArray(); + }, /Invalid pointer/); +}