From c679f6f4c9dc6bf9fc0d99cbe5982bd24a5e2c7b Mon Sep 17 00:00:00 2001 From: Kevin Eady <8634912+KevinEady@users.noreply.github.com> Date: Fri, 15 Nov 2024 18:16:37 +0100 Subject: [PATCH] feat: allow catching all exceptions (#1593) * feat: allow catching all exceptions * Address review comments --- doc/cmake-js.md | 19 ++++++ doc/error_handling.md | 12 ++++ doc/setup.md | 14 ++++- napi-inl.h | 143 +++++++++++++++++++++++++++--------------- napi.h | 71 +++++++++++++-------- node_addon_api.gyp | 10 +++ noexcept.gypi | 2 +- test/binding.gyp | 8 +++ test/except_all.cc | 21 +++++++ test/except_all.js | 14 +++++ 10 files changed, 237 insertions(+), 77 deletions(-) create mode 100644 test/except_all.cc create mode 100644 test/except_all.js diff --git a/doc/cmake-js.md b/doc/cmake-js.md index 85bdf9412..9b79a4a00 100644 --- a/doc/cmake-js.md +++ b/doc/cmake-js.md @@ -45,6 +45,25 @@ The following line in the `CMakeLists.txt` file will enable Node-API experimenta add_definitions(-DNAPI_EXPERIMENTAL) ``` +### Exception Handling + +To enable C++ exception handling (for more info see: [Setup](setup.md)), define +the corresponding preprocessor directives depending on which exception handling +behavior is desired. + +To enable C++ exception handling with `Napi::Error` objects only: + +``` +add_definitions(-DNAPI_EXPERIMENTAL) +``` + +To enable C++ exception handling for all exceptions thrown: + +``` +add_definitions(-DNODE_ADDON_API_CPP_EXCEPTIONS) +add_definitions(-DNODE_ADDON_API_CPP_EXCEPTIONS_ALL) +``` + ### node-addon-api If your Node-API native add-on uses the optional [**node-addon-api**](https://github.com/nodejs/node-addon-api#node-addon-api-module) C++ wrapper, the `CMakeLists.txt` file requires additional configuration information as described on the [CMake.js README file](https://github.com/cmake-js/cmake-js#node-api-and-node-addon-api). diff --git a/doc/error_handling.md b/doc/error_handling.md index 5bb7481aa..b4b4ca238 100644 --- a/doc/error_handling.md +++ b/doc/error_handling.md @@ -48,6 +48,18 @@ method. If a C++ exception of type `Napi::Error` escapes from a Node-API C++ callback, then the Node-API wrapper automatically converts and throws it as a JavaScript exception. +If other types of C++ exceptions are thrown, node-addon-api will either abort +the process or wrap the exception in an `Napi::Error` in order to throw it as a +JavaScript exception. This behavior is determined by which node-gyp dependency +used: + +- When using the `node_addon_api_except` dependency, only `Napi::Error` objects + will be handled. +- When using the `node_addon_api_except_all` dependency, all exceptions will be +handled. For exceptions derived from `std::exception`, an `Napi::Error` will be +created with the message of the exception's `what()` member function. For all +other exceptions, an `Napi::Error` will be created with a generic error message. + On return from a native method, node-addon-api will automatically convert a pending `Napi::Error` C++ exception to a JavaScript exception. diff --git a/doc/setup.md b/doc/setup.md index 86a1b0f38..b3b7effc6 100644 --- a/doc/setup.md +++ b/doc/setup.md @@ -38,7 +38,10 @@ To use **Node-API** in a native module: ], ``` - To enable that capability, add an alternative dependency in `binding.gyp`: + To enable that capability, add an alternative dependency in `binding.gyp` + depending on if you want to integrate C++ exception handling for exceptions + derived from `Napi::Error` or all C++ exceptions. To catch only + `Napi::Error` exceptions, use: ```gyp 'dependencies': [ @@ -46,6 +49,15 @@ To use **Node-API** in a native module: ], ``` + Or, to allow catching all native C++ exceptions, use the + `node_addon_api_except_all` dependency: + + ```gyp + 'dependencies': [ + " -inline napi_value WrapCallback(Callable callback) { -#ifdef NAPI_CPP_EXCEPTIONS +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS_ALL +inline napi_value WrapCallback(napi_env env, Callable callback) { +#else +inline napi_value WrapCallback(napi_env, Callable callback) { +#endif +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS try { return callback(); } catch (const Error& e) { e.ThrowAsJavaScriptException(); return nullptr; } -#else // NAPI_CPP_EXCEPTIONS +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS_ALL + catch (const std::exception& e) { + Napi::Error::New(env, e.what()).ThrowAsJavaScriptException(); + return nullptr; + } catch (...) { + Napi::Error::New(env, "A native exception was thrown") + .ThrowAsJavaScriptException(); + return nullptr; + } +#endif // NODE_ADDON_API_CPP_EXCEPTIONS_ALL +#else // NODE_ADDON_API_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 +#endif // NODE_ADDON_API_CPP_EXCEPTIONS } // For use in JS to C++ void callback wrappers to catch any Napi::Error @@ -99,7 +113,7 @@ inline napi_value WrapCallback(Callable callback) { // the callback. template inline void WrapVoidCallback(Callable callback) { -#ifdef NAPI_CPP_EXCEPTIONS +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS try { callback(); } catch (const Error& e) { @@ -112,10 +126,41 @@ inline void WrapVoidCallback(Callable callback) { #endif // NAPI_CPP_EXCEPTIONS } +// For use in JS to C++ void callback wrappers to catch _any_ thrown exception +// and rethrow them as JavaScript exceptions before returning from the callback, +// wrapping in an Napi::Error as needed. +template +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS_ALL +inline void WrapVoidCallback(napi_env env, Callable callback) { +#else +inline void WrapVoidCallback(napi_env, Callable callback) { +#endif +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS + try { + callback(); + } catch (const Error& e) { + e.ThrowAsJavaScriptException(); + } +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS_ALL + catch (const std::exception& e) { + Napi::Error::New(env, e.what()).ThrowAsJavaScriptException(); + } catch (...) { + Napi::Error::New(env, "A native exception was thrown") + .ThrowAsJavaScriptException(); + } +#endif // NODE_ADDON_API_CPP_EXCEPTIONS_ALL +#else + // When C++ exceptions are disabled, there is no need to catch and rethrow C++ + // exceptions. JS errors should be thrown with + // `Error::ThrowAsJavaScriptException`. + callback(); +#endif // NODE_ADDON_API_CPP_EXCEPTIONS +} + template struct CallbackData { static inline napi_value Wrapper(napi_env env, napi_callback_info info) { - return details::WrapCallback([&] { + return details::WrapCallback(env, [&] { CallbackInfo callbackInfo(env, info); CallbackData* callbackData = static_cast(callbackInfo.Data()); @@ -131,7 +176,7 @@ struct CallbackData { template struct CallbackData { static inline napi_value Wrapper(napi_env env, napi_callback_info info) { - return details::WrapCallback([&] { + return details::WrapCallback(env, [&] { CallbackInfo callbackInfo(env, info); CallbackData* callbackData = static_cast(callbackInfo.Data()); @@ -148,7 +193,7 @@ struct CallbackData { template napi_value TemplatedVoidCallback(napi_env env, napi_callback_info info) NAPI_NOEXCEPT { - return details::WrapCallback([&] { + return details::WrapCallback(env, [&] { CallbackInfo cbInfo(env, info); Callback(cbInfo); return nullptr; @@ -158,7 +203,7 @@ napi_value TemplatedVoidCallback(napi_env env, template napi_value TemplatedCallback(napi_env env, napi_callback_info info) NAPI_NOEXCEPT { - return details::WrapCallback([&] { + return details::WrapCallback(env, [&] { CallbackInfo cbInfo(env, info); // MSVC requires to copy 'Callback' function pointer to a local variable // before invoking it. @@ -171,7 +216,7 @@ template napi_value TemplatedInstanceCallback(napi_env env, napi_callback_info info) NAPI_NOEXCEPT { - return details::WrapCallback([&] { + return details::WrapCallback(env, [&] { CallbackInfo cbInfo(env, info); T* instance = T::Unwrap(cbInfo.This().As()); return instance ? (instance->*UnwrapCallback)(cbInfo) : Napi::Value(); @@ -181,7 +226,7 @@ napi_value TemplatedInstanceCallback(napi_env env, template napi_value TemplatedInstanceVoidCallback(napi_env env, napi_callback_info info) NAPI_NOEXCEPT { - return details::WrapCallback([&] { + return details::WrapCallback(env, [&] { CallbackInfo cbInfo(env, info); T* instance = T::Unwrap(cbInfo.This().As()); if (instance) (instance->*UnwrapCallback)(cbInfo); @@ -264,7 +309,7 @@ struct FinalizeData { static inline void WrapperGCWithoutData(napi_env env, void* /*data*/, void* finalizeHint) NAPI_NOEXCEPT { - WrapVoidCallback([&] { + WrapVoidCallback(env, [&] { FinalizeData* finalizeData = static_cast(finalizeHint); finalizeData->callback(env); delete finalizeData; @@ -274,7 +319,7 @@ struct FinalizeData { static inline void WrapperGC(napi_env env, void* data, void* finalizeHint) NAPI_NOEXCEPT { - WrapVoidCallback([&] { + WrapVoidCallback(env, [&] { FinalizeData* finalizeData = static_cast(finalizeHint); finalizeData->callback(env, static_cast(data)); delete finalizeData; @@ -284,7 +329,7 @@ struct FinalizeData { static inline void WrapperGCWithHint(napi_env env, void* data, void* finalizeHint) NAPI_NOEXCEPT { - WrapVoidCallback([&] { + WrapVoidCallback(env, [&] { FinalizeData* finalizeData = static_cast(finalizeHint); finalizeData->callback(env, static_cast(data), finalizeData->hint); delete finalizeData; @@ -351,7 +396,7 @@ struct ThreadSafeFinalize { template inline typename std::enable_if(nullptr)>::type CallJsWrapper(napi_env env, napi_value jsCallback, void* context, void* data) { - details::WrapVoidCallback([&]() { + details::WrapVoidCallback(env, [&]() { call(env, Function(env, jsCallback), static_cast(context), @@ -365,7 +410,7 @@ CallJsWrapper(napi_env env, napi_value jsCallback, void* /*context*/, void* /*data*/) { - details::WrapVoidCallback([&]() { + details::WrapVoidCallback(env, [&]() { if (jsCallback != nullptr) { Function(env, jsCallback).Call(0, nullptr); } @@ -399,7 +444,7 @@ template struct AccessorCallbackData { static inline napi_value GetterWrapper(napi_env env, napi_callback_info info) { - return details::WrapCallback([&] { + return details::WrapCallback(env, [&] { CallbackInfo callbackInfo(env, info); AccessorCallbackData* callbackData = static_cast(callbackInfo.Data()); @@ -410,7 +455,7 @@ struct AccessorCallbackData { static inline napi_value SetterWrapper(napi_env env, napi_callback_info info) { - return details::WrapCallback([&] { + return details::WrapCallback(env, [&] { CallbackInfo callbackInfo(env, info); AccessorCallbackData* callbackData = static_cast(callbackInfo.Data()); @@ -501,7 +546,7 @@ class HasBasicFinalizer { inline napi_value RegisterModule(napi_env env, napi_value exports, ModuleRegisterCallback registerCallback) { - return details::WrapCallback([&] { + return details::WrapCallback(env, [&] { return napi_value( registerCallback(Napi::Env(env), Napi::Object(env, exports))); }); @@ -1808,7 +1853,7 @@ inline void Object::AddFinalizer(Finalizer finalizeCallback, } } -#ifdef NAPI_CPP_EXCEPTIONS +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS inline Object::const_iterator::const_iterator(const Object* object, const Type type) { _object = object; @@ -1883,7 +1928,7 @@ Object::iterator::operator*() { PropertyLValue value = (*_object)[key]; return {key, value}; } -#endif // NAPI_CPP_EXCEPTIONS +#endif // NODE_ADDON_API_CPP_EXCEPTIONS #if NAPI_VERSION >= 8 inline MaybeOrValue Object::Freeze() const { @@ -3159,14 +3204,14 @@ inline Error& Error::operator=(const Error& other) { inline const std::string& Error::Message() const NAPI_NOEXCEPT { if (_message.size() == 0 && _env != nullptr) { -#ifdef NAPI_CPP_EXCEPTIONS +#ifdef NODE_ADDON_API_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 method may not throw. } -#else // NAPI_CPP_EXCEPTIONS +#else // NODE_ADDON_API_CPP_EXCEPTIONS #if defined(NODE_ADDON_API_ENABLE_MAYBE) Napi::Value message_val; if (Get("message").UnwrapTo(&message_val)) { @@ -3175,7 +3220,7 @@ inline const std::string& Error::Message() const NAPI_NOEXCEPT { #else _message = Get("message").As(); #endif -#endif // NAPI_CPP_EXCEPTIONS +#endif // NODE_ADDON_API_CPP_EXCEPTIONS } return _message; } @@ -3222,24 +3267,24 @@ inline void Error::ThrowAsJavaScriptException() const { napi_status status = napi_throw(_env, Value()); #endif -#ifdef NAPI_CPP_EXCEPTIONS +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS if (status != napi_ok) { throw Error::New(_env); } -#else // NAPI_CPP_EXCEPTIONS +#else // NODE_ADDON_API_CPP_EXCEPTIONS NAPI_FATAL_IF_FAILED( status, "Error::ThrowAsJavaScriptException", "napi_throw"); -#endif // NAPI_CPP_EXCEPTIONS +#endif // NODE_ADDON_API_CPP_EXCEPTIONS } } -#ifdef NAPI_CPP_EXCEPTIONS +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS inline const char* Error::what() const NAPI_NOEXCEPT { return Message().c_str(); } -#endif // NAPI_CPP_EXCEPTIONS +#endif // NODE_ADDON_API_CPP_EXCEPTIONS inline const char* Error::ERROR_WRAP_VALUE() NAPI_NOEXCEPT { return "4bda9e7e-4913-4dbc-95de-891cbf66598e-errorVal"; @@ -4508,7 +4553,7 @@ inline ClassPropertyDescriptor InstanceWrap::InstanceValue( template inline napi_value InstanceWrap::InstanceVoidMethodCallbackWrapper( napi_env env, napi_callback_info info) { - return details::WrapCallback([&] { + return details::WrapCallback(env, [&] { CallbackInfo callbackInfo(env, info); InstanceVoidMethodCallbackData* callbackData = reinterpret_cast(callbackInfo.Data()); @@ -4523,7 +4568,7 @@ inline napi_value InstanceWrap::InstanceVoidMethodCallbackWrapper( template inline napi_value InstanceWrap::InstanceMethodCallbackWrapper( napi_env env, napi_callback_info info) { - return details::WrapCallback([&] { + return details::WrapCallback(env, [&] { CallbackInfo callbackInfo(env, info); InstanceMethodCallbackData* callbackData = reinterpret_cast(callbackInfo.Data()); @@ -4537,7 +4582,7 @@ inline napi_value InstanceWrap::InstanceMethodCallbackWrapper( template inline napi_value InstanceWrap::InstanceGetterCallbackWrapper( napi_env env, napi_callback_info info) { - return details::WrapCallback([&] { + return details::WrapCallback(env, [&] { CallbackInfo callbackInfo(env, info); InstanceAccessorCallbackData* callbackData = reinterpret_cast(callbackInfo.Data()); @@ -4551,7 +4596,7 @@ inline napi_value InstanceWrap::InstanceGetterCallbackWrapper( template inline napi_value InstanceWrap::InstanceSetterCallbackWrapper( napi_env env, napi_callback_info info) { - return details::WrapCallback([&] { + return details::WrapCallback(env, [&] { CallbackInfo callbackInfo(env, info); InstanceAccessorCallbackData* callbackData = reinterpret_cast(callbackInfo.Data()); @@ -4567,7 +4612,7 @@ template template ::InstanceSetterCallback method> inline napi_value InstanceWrap::WrappedMethod( napi_env env, napi_callback_info info) NAPI_NOEXCEPT { - return details::WrapCallback([&] { + return details::WrapCallback(env, [&] { const CallbackInfo cbInfo(env, info); T* instance = T::Unwrap(cbInfo.This().As()); if (instance) (instance->*method)(cbInfo, cbInfo[0]); @@ -4962,13 +5007,13 @@ inline napi_value ObjectWrap::ConstructorCallbackWrapper( bool isConstructCall = (new_target != nullptr); if (!isConstructCall) { return details::WrapCallback( - [&] { return T::OnCalledAsFunction(CallbackInfo(env, info)); }); + env, [&] { return T::OnCalledAsFunction(CallbackInfo(env, info)); }); } - napi_value wrapper = details::WrapCallback([&] { + napi_value wrapper = details::WrapCallback(env, [&] { CallbackInfo callbackInfo(env, info); T* instance = new T(callbackInfo); -#ifdef NAPI_CPP_EXCEPTIONS +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS instance->_construction_failed = false; #else if (callbackInfo.Env().IsExceptionPending()) { @@ -4979,7 +5024,7 @@ inline napi_value ObjectWrap::ConstructorCallbackWrapper( } else { instance->_construction_failed = false; } -#endif // NAPI_CPP_EXCEPTIONS +#endif // NODE_ADDON_API_CPP_EXCEPTIONS return callbackInfo.This(); }); @@ -4989,7 +5034,7 @@ inline napi_value ObjectWrap::ConstructorCallbackWrapper( template inline napi_value ObjectWrap::StaticVoidMethodCallbackWrapper( napi_env env, napi_callback_info info) { - return details::WrapCallback([&] { + return details::WrapCallback(env, [&] { CallbackInfo callbackInfo(env, info); StaticVoidMethodCallbackData* callbackData = reinterpret_cast(callbackInfo.Data()); @@ -5002,7 +5047,7 @@ inline napi_value ObjectWrap::StaticVoidMethodCallbackWrapper( template inline napi_value ObjectWrap::StaticMethodCallbackWrapper( napi_env env, napi_callback_info info) { - return details::WrapCallback([&] { + return details::WrapCallback(env, [&] { CallbackInfo callbackInfo(env, info); StaticMethodCallbackData* callbackData = reinterpret_cast(callbackInfo.Data()); @@ -5014,7 +5059,7 @@ inline napi_value ObjectWrap::StaticMethodCallbackWrapper( template inline napi_value ObjectWrap::StaticGetterCallbackWrapper( napi_env env, napi_callback_info info) { - return details::WrapCallback([&] { + return details::WrapCallback(env, [&] { CallbackInfo callbackInfo(env, info); StaticAccessorCallbackData* callbackData = reinterpret_cast(callbackInfo.Data()); @@ -5026,7 +5071,7 @@ inline napi_value ObjectWrap::StaticGetterCallbackWrapper( template inline napi_value ObjectWrap::StaticSetterCallbackWrapper( napi_env env, napi_callback_info info) { - return details::WrapCallback([&] { + return details::WrapCallback(env, [&] { CallbackInfo callbackInfo(env, info); StaticAccessorCallbackData* callbackData = reinterpret_cast(callbackInfo.Data()); @@ -5101,7 +5146,7 @@ template template ::StaticSetterCallback method> inline napi_value ObjectWrap::WrappedMethod( napi_env env, napi_callback_info info) NAPI_NOEXCEPT { - return details::WrapCallback([&] { + return details::WrapCallback(env, [&] { const CallbackInfo cbInfo(env, info); // MSVC requires to copy 'method' function pointer to a local variable // before invoking it. @@ -5402,15 +5447,15 @@ inline void AsyncWorker::OnAsyncWorkExecute(napi_env env, void* asyncworker) { // must not run any method that would cause JavaScript to run. In practice, // this means that almost any use of napi_env will be incorrect. inline void AsyncWorker::OnExecute(Napi::Env /*DO_NOT_USE*/) { -#ifdef NAPI_CPP_EXCEPTIONS +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS try { Execute(); } catch (const std::exception& e) { SetError(e.what()); } -#else // NAPI_CPP_EXCEPTIONS +#else // NODE_ADDON_API_CPP_EXCEPTIONS Execute(); -#endif // NAPI_CPP_EXCEPTIONS +#endif // NODE_ADDON_API_CPP_EXCEPTIONS } inline void AsyncWorker::OnAsyncWorkComplete(napi_env env, @@ -5419,10 +5464,10 @@ inline void AsyncWorker::OnAsyncWorkComplete(napi_env env, AsyncWorker* self = static_cast(asyncworker); self->OnWorkComplete(env, status); } -inline void AsyncWorker::OnWorkComplete(Napi::Env /*env*/, napi_status status) { +inline void AsyncWorker::OnWorkComplete(Napi::Env env, napi_status status) { if (status != napi_cancelled) { HandleScope scope(_env); - details::WrapCallback([&] { + details::WrapCallback(env, [&] { if (_error.size() == 0) { OnOK(); } else { @@ -6334,7 +6379,7 @@ inline void ThreadSafeFunction::CallJS(napi_env env, return; } - details::WrapVoidCallback([&]() { + details::WrapVoidCallback(env, [&]() { if (data != nullptr) { auto* callbackWrapper = static_cast(data); (*callbackWrapper)(env, Function(env, jsCallback)); diff --git a/napi.h b/napi.h index 99488f94e..4838be6cc 100644 --- a/napi.h +++ b/napi.h @@ -37,22 +37,40 @@ static_assert(sizeof(char16_t) == sizeof(wchar_t), #define NAPI_WIDE_TEXT(x) u##x #endif +// Backwards-compatibility to handle the rename of this macro definition, in +// case they are used within userland code. +#ifdef NAPI_CPP_EXCEPTIONS +#define NODE_ADDON_API_CPP_EXCEPTIONS +#endif +#if defined(NODE_ADDON_API_CPP_EXCEPTIONS) && !defined(NAPI_CPP_EXCEPTIONS) +#define NAPI_CPP_EXCEPTIONS +#endif +#ifdef NAPI_DISABLE_CPP_EXCEPTIONS +#define NODE_ADDON_API_DISABLE_CPP_EXCEPTIONS +#endif +#if defined(NODE_ADDON_API_DISABLE_CPP_EXCEPTIONS) && \ + !defined(NAPI_DISABLE_CPP_EXCEPTIONS) +#define NAPI_DISABLE_CPP_EXCEPTIONS +#endif + // 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(NODE_ADDON_API_CPP_EXCEPTIONS) && \ + !defined(NODE_ADDON_API_DISABLE_CPP_EXCEPTIONS) #if defined(_CPPUNWIND) || defined(__EXCEPTIONS) -#define NAPI_CPP_EXCEPTIONS +#define NODE_ADDON_API_CPP_EXCEPTIONS #else #error Exception support not detected. \ - Define either NAPI_CPP_EXCEPTIONS or NAPI_DISABLE_CPP_EXCEPTIONS. + Define either NODE_ADDON_API_CPP_EXCEPTIONS or NODE_ADDON_API_DISABLE_CPP_EXCEPTIONS. #endif #endif -// If C++ NAPI_CPP_EXCEPTIONS are enabled, NODE_ADDON_API_ENABLE_MAYBE should -// not be set -#if defined(NAPI_CPP_EXCEPTIONS) && defined(NODE_ADDON_API_ENABLE_MAYBE) +// If C++ NODE_ADDON_API_CPP_EXCEPTIONS are enabled, NODE_ADDON_API_ENABLE_MAYBE +// should not be set +#if defined(NODE_ADDON_API_CPP_EXCEPTIONS) && \ + defined(NODE_ADDON_API_ENABLE_MAYBE) #error NODE_ADDON_API_ENABLE_MAYBE should not be set when \ - NAPI_CPP_EXCEPTIONS is defined. + NODE_ADDON_API_CPP_EXCEPTIONS is defined. #endif #ifdef _NOEXCEPT @@ -61,7 +79,7 @@ static_assert(sizeof(char16_t) == sizeof(wchar_t), #define NAPI_NOEXCEPT noexcept #endif -#ifdef NAPI_CPP_EXCEPTIONS +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS // When C++ exceptions are enabled, Errors are thrown directly. There is no need // to return anything after the throw statements. The variadic parameter is an @@ -78,7 +96,7 @@ static_assert(sizeof(char16_t) == sizeof(wchar_t), #define NAPI_THROW_IF_FAILED_VOID(env, status) \ if ((status) != napi_ok) throw Napi::Error::New(env); -#else // NAPI_CPP_EXCEPTIONS +#else // NODE_ADDON_API_CPP_EXCEPTIONS // When C++ exceptions are disabled, Errors are thrown as JavaScript exceptions, // which are pending until the callback returns to JS. The variadic parameter @@ -110,7 +128,7 @@ static_assert(sizeof(char16_t) == sizeof(wchar_t), return; \ } -#endif // NAPI_CPP_EXCEPTIONS +#endif // NODE_ADDON_API_CPP_EXCEPTIONS #ifdef NODE_ADDON_API_ENABLE_MAYBE #define NAPI_MAYBE_THROW_IF_FAILED(env, status, type) \ @@ -1068,7 +1086,7 @@ class Object : public TypeTaggable { T* data, Hint* finalizeHint) const; -#ifdef NAPI_CPP_EXCEPTIONS +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS class const_iterator; inline const_iterator begin() const; @@ -1080,7 +1098,7 @@ class Object : public TypeTaggable { inline iterator begin(); inline iterator end(); -#endif // NAPI_CPP_EXCEPTIONS +#endif // NODE_ADDON_API_CPP_EXCEPTIONS #if NAPI_VERSION >= 8 /// This operation can fail in case of Proxy.[[GetPrototypeOf]] calling into @@ -1132,7 +1150,7 @@ class Array : public Object { uint32_t Length() const; }; -#ifdef NAPI_CPP_EXCEPTIONS +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS class Object::const_iterator { private: enum class Type { BEGIN, END }; @@ -1179,7 +1197,7 @@ class Object::iterator { friend class Object; }; -#endif // NAPI_CPP_EXCEPTIONS +#endif // NODE_ADDON_API_CPP_EXCEPTIONS /// A JavaScript array buffer value. class ArrayBuffer : public Object { @@ -1811,14 +1829,15 @@ FunctionReference Persistent(Function value); /// /// ### 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. +/// If C++ exceptions are disabled (by defining +/// `NODE_ADDON_API_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 /// @@ -1853,10 +1872,10 @@ FunctionReference Persistent(Function value); /// 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 +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS , public std::exception -#endif // NAPI_CPP_EXCEPTIONS +#endif // NODE_ADDON_API_CPP_EXCEPTIONS { public: static Error New(napi_env env); @@ -1879,9 +1898,9 @@ class Error : public ObjectReference Object Value() const; -#ifdef NAPI_CPP_EXCEPTIONS +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS const char* what() const NAPI_NOEXCEPT override; -#endif // NAPI_CPP_EXCEPTIONS +#endif // NODE_ADDON_API_CPP_EXCEPTIONS protected: /// !cond INTERNAL diff --git a/node_addon_api.gyp b/node_addon_api.gyp index 29905ed47..8c099262a 100644 --- a/node_addon_api.gyp +++ b/node_addon_api.gyp @@ -18,6 +18,16 @@ 'includes': ['except.gypi'], } }, + { + 'target_name': 'node_addon_api_except_all', + 'type': 'none', + 'sources': [ 'napi.h', 'napi-inl.h' ], + 'direct_dependent_settings': { + 'include_dirs': [ '.' ], + 'includes': ['except.gypi'], + 'defines': [ 'NODE_ADDON_API_CPP_EXCEPTIONS_ALL' ] + } + }, { 'target_name': 'node_addon_api_maybe', 'type': 'none', diff --git a/noexcept.gypi b/noexcept.gypi index 404a05f30..83df4ddf0 100644 --- a/noexcept.gypi +++ b/noexcept.gypi @@ -1,5 +1,5 @@ { - 'defines': [ 'NAPI_DISABLE_CPP_EXCEPTIONS' ], + 'defines': [ 'NODE_ADDON_API_DISABLE_CPP_EXCEPTIONS' ], 'cflags': [ '-fno-exceptions' ], 'cflags_cc': [ '-fno-exceptions' ], 'conditions': [ diff --git a/test/binding.gyp b/test/binding.gyp index 3dd861d0b..8ee391fb9 100644 --- a/test/binding.gyp +++ b/test/binding.gyp @@ -85,6 +85,9 @@ 'binding-swallowexcept.cc', 'error.cc', ], + 'build_sources_except_all': [ + 'except_all.cc', + ], 'build_sources_type_check': [ 'value_type_cast.cc' ], @@ -114,6 +117,11 @@ 'sources': ['>@(build_sources)'], 'defines': ['NODE_ADDON_API_ENABLE_TYPE_CHECK_ON_AS'] }, + { + 'target_name': 'binding_except_all', + 'dependencies': ['../node_addon_api.gyp:node_addon_api_except_all'], + 'sources': [ '>@(build_sources_except_all)'] + }, { 'target_name': 'binding_noexcept', 'dependencies': ['../node_addon_api.gyp:node_addon_api'], diff --git a/test/except_all.cc b/test/except_all.cc new file mode 100644 index 000000000..ca91efdf3 --- /dev/null +++ b/test/except_all.cc @@ -0,0 +1,21 @@ +#include "napi.h" + +using namespace Napi; + +void ThrowStdException(const CallbackInfo& info) { + std::string message = info[0].As().Utf8Value(); + throw std::runtime_error(message); +} + +void ThrowPrimitiveException(const CallbackInfo&) { + throw 0; +} + +Object Init(Env env, Object exports) { + exports.Set("throwStdException", Napi::Function::New(env, ThrowStdException)); + exports.Set("throwPrimitiveException", + Napi::Function::New(env, ThrowPrimitiveException)); + return exports; +} + +NODE_API_MODULE(addon, Init) diff --git a/test/except_all.js b/test/except_all.js new file mode 100644 index 000000000..d650ece6f --- /dev/null +++ b/test/except_all.js @@ -0,0 +1,14 @@ +'use strict'; + +const assert = require('assert'); + +module.exports = require('./common').runTestWithBuildType(test); + +function test (buildType) { + const binding = require(`./build/${buildType}/binding_except_all.node`); + + const message = 'error message'; + assert.throws(binding.throwStdException.bind(undefined, message), { message }); + + assert.throws(binding.throwPrimitiveException.bind(undefined), { message: 'A native exception was thrown' }); +}