From 29a04b7ed69d5496b071badb82307964cd018552 Mon Sep 17 00:00:00 2001 From: Mathias Buus Date: Wed, 14 Mar 2018 03:14:08 +0100 Subject: [PATCH] n-api: add napi_fatal_exception Add function to trigger and uncaught exception. Useful if an async callback throws an exception with no way to recover. PR-URL: https://github.com/nodejs/node/pull/19337 Reviewed-By: James M Snell Reviewed-By: Colin Ihrig Reviewed-By: Anna Henningsen Reviewed-By: Michael Dawson --- doc/api/n-api.md | 14 ++++++++++ src/node_api.cc | 25 +++++++++++++++--- src/node_api.h | 2 ++ src/node_internals.h | 5 ++++ test/addons-napi/test_async/test-uncaught.js | 18 +++++++++++++ .../test_fatal_exception/binding.gyp | 8 ++++++ test/addons-napi/test_fatal_exception/test.js | 11 ++++++++ .../test_fatal_exception.c | 26 +++++++++++++++++++ 8 files changed, 105 insertions(+), 4 deletions(-) create mode 100644 test/addons-napi/test_async/test-uncaught.js create mode 100644 test/addons-napi/test_fatal_exception/binding.gyp create mode 100644 test/addons-napi/test_fatal_exception/test.js create mode 100644 test/addons-napi/test_fatal_exception/test_fatal_exception.c diff --git a/doc/api/n-api.md b/doc/api/n-api.md index fc4db1038be2a9..4b93973dbd6bc2 100644 --- a/doc/api/n-api.md +++ b/doc/api/n-api.md @@ -541,6 +541,20 @@ This API returns true if an exception is pending. This API can be called even if there is a pending JavaScript exception. +#### napi_fatal_exception + +```C +napi_status napi_fatal_exception(napi_env env, napi_value err); +``` + +- `[in] env`: The environment that the API is invoked under. +- `[in] err`: The error you want to pass to `uncaughtException`. + +Trigger an `uncaughtException` in JavaScript. Useful if an async +callback throws an exception with no way to recover. + ### Fatal Errors In the event of an unrecoverable error in a native module, a fatal error can be diff --git a/src/node_api.cc b/src/node_api.cc index fa6d09dbd9cbb5..ea6bccef3f77de 100644 --- a/src/node_api.cc +++ b/src/node_api.cc @@ -167,6 +167,7 @@ struct napi_env__ { (out) = v8::type::New((buffer), (byte_offset), (length)); \ } while (0) + namespace { namespace v8impl { @@ -289,6 +290,13 @@ v8::Local V8LocalValueFromJsValue(napi_value v) { return local; } +static inline void trigger_fatal_exception( + napi_env env, v8::Local local_err) { + v8::Local local_msg = + v8::Exception::CreateMessage(env->isolate, local_err); + node::FatalException(env->isolate, local_err, local_msg); +} + static inline napi_status V8NameFromPropertyDescriptor(napi_env env, const napi_property_descriptor* p, v8::Local* result) { @@ -954,6 +962,16 @@ napi_status napi_get_last_error_info(napi_env env, return napi_ok; } +napi_status napi_fatal_exception(napi_env env, napi_value err) { + NAPI_PREAMBLE(env); + CHECK_ARG(env, err); + + v8::Local local_err = v8impl::V8LocalValueFromJsValue(err); + v8impl::trigger_fatal_exception(env, local_err); + + return napi_clear_last_error(env); +} + NAPI_NO_RETURN void napi_fatal_error(const char* location, size_t location_len, const char* message, @@ -3358,10 +3376,9 @@ class Work : public node::AsyncResource { // report it as a fatal exception. (There is no JavaScript on the // callstack that can possibly handle it.) if (!env->last_exception.IsEmpty()) { - v8::TryCatch try_catch(env->isolate); - env->isolate->ThrowException( - v8::Local::New(env->isolate, env->last_exception)); - node::FatalException(env->isolate, try_catch); + v8::Local local_err = v8::Local::New( + env->isolate, env->last_exception); + v8impl::trigger_fatal_exception(env, local_err); } } } diff --git a/src/node_api.h b/src/node_api.h index e9b3645e404530..d2a0886d5a058c 100644 --- a/src/node_api.h +++ b/src/node_api.h @@ -112,6 +112,8 @@ NAPI_EXTERN napi_status napi_get_last_error_info(napi_env env, const napi_extended_error_info** result); +NAPI_EXTERN napi_status napi_fatal_exception(napi_env env, napi_value err); + NAPI_EXTERN NAPI_NO_RETURN void napi_fatal_error(const char* location, size_t location_len, const char* message, diff --git a/src/node_internals.h b/src/node_internals.h index af716c83997c78..46a1be4e19b433 100644 --- a/src/node_internals.h +++ b/src/node_internals.h @@ -248,6 +248,11 @@ void GetSockOrPeerName(const v8::FunctionCallbackInfo& args) { args.GetReturnValue().Set(err); } +void FatalException(v8::Isolate* isolate, + v8::Local error, + v8::Local message); + + void SignalExit(int signo); #ifdef __POSIX__ void RegisterSignalHandler(int signal, diff --git a/test/addons-napi/test_async/test-uncaught.js b/test/addons-napi/test_async/test-uncaught.js new file mode 100644 index 00000000000000..fdcb3203f54410 --- /dev/null +++ b/test/addons-napi/test_async/test-uncaught.js @@ -0,0 +1,18 @@ +'use strict'; +const common = require('../../common'); +const assert = require('assert'); +const test_async = require(`./build/${common.buildType}/test_async`); + +process.on('uncaughtException', common.mustCall(function(err) { + try { + throw new Error('should not fail'); + } catch (err) { + assert.strictEqual(err.message, 'should not fail'); + } + assert.strictEqual(err.message, 'uncaught'); +})); + +// Successful async execution and completion callback. +test_async.Test(5, {}, common.mustCall(function() { + throw new Error('uncaught'); +})); diff --git a/test/addons-napi/test_fatal_exception/binding.gyp b/test/addons-napi/test_fatal_exception/binding.gyp new file mode 100644 index 00000000000000..f4dc0a71ea2817 --- /dev/null +++ b/test/addons-napi/test_fatal_exception/binding.gyp @@ -0,0 +1,8 @@ +{ + "targets": [ + { + "target_name": "test_fatal_exception", + "sources": [ "test_fatal_exception.c" ] + } + ] +} diff --git a/test/addons-napi/test_fatal_exception/test.js b/test/addons-napi/test_fatal_exception/test.js new file mode 100644 index 00000000000000..f02b9bce1e8169 --- /dev/null +++ b/test/addons-napi/test_fatal_exception/test.js @@ -0,0 +1,11 @@ +'use strict'; +const common = require('../../common'); +const assert = require('assert'); +const test_fatal = require(`./build/${common.buildType}/test_fatal_exception`); + +process.on('uncaughtException', common.mustCall(function(err) { + assert.strictEqual(err.message, 'fatal error'); +})); + +const err = new Error('fatal error'); +test_fatal.Test(err); diff --git a/test/addons-napi/test_fatal_exception/test_fatal_exception.c b/test/addons-napi/test_fatal_exception/test_fatal_exception.c new file mode 100644 index 00000000000000..fd81c56d856db8 --- /dev/null +++ b/test/addons-napi/test_fatal_exception/test_fatal_exception.c @@ -0,0 +1,26 @@ +#include +#include "../common.h" + +napi_value Test(napi_env env, napi_callback_info info) { + napi_value err; + size_t argc = 1; + + NAPI_CALL(env, napi_get_cb_info(env, info, &argc, &err, NULL, NULL)); + + NAPI_CALL(env, napi_fatal_exception(env, err)); + + return NULL; +} + +napi_value Init(napi_env env, napi_value exports) { + napi_property_descriptor properties[] = { + DECLARE_NAPI_PROPERTY("Test", Test), + }; + + NAPI_CALL(env, napi_define_properties( + env, exports, sizeof(properties) / sizeof(*properties), properties)); + + return exports; +} + +NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)