Skip to content

Commit b2fd523

Browse files
node-api: Add status napi_cannot_run_js
Add the new status in order to distinguish a state wherein an exception is pending from one wherein the engine is unable to execute JS. We take advantage of the new runtime add-on version reporting in order to remain forward compatible with add-ons that do not expect the new status code.
1 parent 2dd6d76 commit b2fd523

File tree

7 files changed

+107
-8
lines changed

7 files changed

+107
-8
lines changed

doc/api/n-api.md

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -580,7 +580,8 @@ typedef enum {
580580
napi_arraybuffer_expected,
581581
napi_detachable_arraybuffer_expected,
582582
napi_would_deadlock, /* unused */
583-
napi_no_external_buffers_allowed
583+
napi_no_external_buffers_allowed,
584+
napi_cannot_run_js
584585
} napi_status;
585586
```
586587

@@ -814,6 +815,18 @@ typedef void (*napi_finalize)(napi_env env,
814815
Unless for reasons discussed in [Object Lifetime Management][], creating a
815816
handle and/or callback scope inside the function body is not necessary.
816817

818+
Since these functions may be called while the JavaScript engine is in a state
819+
where it cannot execute JavaScript code, some Node-API calls may return
820+
`napi_pending_exception` even when there is no exception pending.
821+
822+
Change History:
823+
824+
* experimental:
825+
826+
Node-API calls made from a finalizer will return `napi_cannot_run_js` when
827+
the JavaScript engine is unable to execute JavaScript, and will return
828+
`napi_exception_pending` if there is a pending exception.
829+
817830
#### `napi_async_execute_callback`
818831

819832
<!-- YAML
@@ -1952,11 +1965,11 @@ from [`napi_add_async_cleanup_hook`][].
19521965
The Node.js environment may be torn down at an arbitrary time as soon as
19531966
possible with JavaScript execution disallowed, like on the request of
19541967
[`worker.terminate()`][]. When the environment is being torn down, the
1955-
registered `napi_finalize` callbacks of JavaScript objects, Thread-safe
1968+
registered `napi_finalize` callbacks of JavaScript objects, thread-safe
19561969
functions and environment instance data are invoked immediately and
19571970
independently.
19581971

1959-
The invocation of `napi_finalize` callbacks are scheduled after the manually
1972+
The invocation of `napi_finalize` callbacks is scheduled after the manually
19601973
registered cleanup hooks. In order to ensure a proper order of addon
19611974
finalization during environment shutdown to avoid use-after-free in the
19621975
`napi_finalize` callback, addons should register a cleanup hook with

src/js_native_api_types.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,8 @@ typedef enum {
9999
napi_arraybuffer_expected,
100100
napi_detachable_arraybuffer_expected,
101101
napi_would_deadlock, // unused
102-
napi_no_external_buffers_allowed
102+
napi_no_external_buffers_allowed,
103+
napi_cannot_run_js,
103104
} napi_status;
104105
// Note: when adding a new enum value to `napi_status`, please also update
105106
// * `const int last_status` in the definition of `napi_get_last_error_info()'

src/js_native_api_v8.cc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -682,6 +682,7 @@ static const char* error_messages[] = {
682682
"A detachable arraybuffer was expected",
683683
"Main thread would deadlock",
684684
"External buffers are not allowed",
685+
"Cannot run JavaScript",
685686
};
686687

687688
napi_status NAPI_CDECL napi_get_last_error_info(
@@ -693,7 +694,7 @@ napi_status NAPI_CDECL napi_get_last_error_info(
693694
// message in the `napi_status` enum each time a new error message is added.
694695
// We don't have a napi_status_last as this would result in an ABI
695696
// change each time a message was added.
696-
const int last_status = napi_no_external_buffers_allowed;
697+
const int last_status = napi_cannot_run_js;
697698

698699
static_assert(NAPI_ARRAYSIZE(error_messages) == last_status + 1,
699700
"Count of error messages must match count of error values");

src/js_native_api_v8.h

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -212,9 +212,12 @@ inline napi_status napi_set_last_error(napi_env env,
212212
#define NAPI_PREAMBLE(env) \
213213
CHECK_ENV((env)); \
214214
RETURN_STATUS_IF_FALSE( \
215-
(env), \
216-
(env)->last_exception.IsEmpty() && (env)->can_call_into_js(), \
217-
napi_pending_exception); \
215+
(env), (env)->last_exception.IsEmpty(), napi_pending_exception); \
216+
RETURN_STATUS_IF_FALSE((env), \
217+
(env)->can_call_into_js(), \
218+
(env->module_api_version == NAPI_VERSION_EXPERIMENTAL \
219+
? napi_cannot_run_js \
220+
: napi_pending_exception)); \
218221
napi_clear_last_error((env)); \
219222
v8impl::TryCatch try_catch((env))
220223

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"targets": [
3+
{
4+
"target_name": "test_cannot_run_js",
5+
"sources": [ "test_cannot_run_js.c" ],
6+
"defines": [ "NAPI_EXPERIMENTAL" ],
7+
},
8+
{
9+
"target_name": "test_pending_exception",
10+
"sources": [ "test_cannot_run_js.c" ],
11+
"defines": [ "NAPI_VERSION=8" ],
12+
}
13+
]
14+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
'use strict';
2+
3+
// Test that `napi_call_function()` returns `napi_cannot_run_js` in experimental
4+
// mode and `napi_pending_exception` otherwise. This test calls the add-on's
5+
// `createRef()` method, which creates a strong reference to a JS function. When
6+
// the process exits, it calls all reference finalizers. The finalizer for the
7+
// strong reference created herein will attempt to call `napi_call_function()`
8+
// and will abort the process if the API doesn't return the correct status.
9+
10+
const { buildType } = require('../../common');
11+
const addon_v8 = require(`./build/${buildType}/test_pending_exception`);
12+
const addon_new = require(`./build/${buildType}/test_cannot_run_js`);
13+
14+
function runTests(addon, isVersion8) {
15+
addon.createRef(runTests);
16+
}
17+
18+
function runAllTests() {
19+
runTests(addon_v8, /* isVersion8 */ true);
20+
runTests(addon_new, /* isVersion8 */ false);
21+
}
22+
23+
runAllTests();
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
#include <node_api.h>
2+
#include "../../js-native-api/common.h"
3+
#include "stdlib.h"
4+
5+
static void Finalize(napi_env env, void* data, void* hint) {
6+
napi_value cb;
7+
napi_ref* ref = data;
8+
#ifdef NAPI_EXPERIMENTAL
9+
napi_status expected_status = napi_cannot_run_js;
10+
#else
11+
napi_status expected_status = napi_pending_exception;
12+
#endif // NAPI_EXPERIMENTAL
13+
14+
if (napi_get_reference_value(env, *ref, &cb) != napi_ok) abort();
15+
if (napi_delete_reference(env, *ref) != napi_ok) abort();
16+
if (napi_call_function(env, cb, cb, 0, NULL, NULL) != expected_status) abort();
17+
free(ref);
18+
}
19+
20+
static napi_value CreateRef(napi_env env, napi_callback_info info) {
21+
size_t argc = 1;
22+
napi_value cb;
23+
napi_valuetype value_type;
24+
napi_ref* ref = malloc(sizeof(*ref));
25+
NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, &cb, NULL, NULL));
26+
NODE_API_ASSERT(env, argc == 1, "Function takes only one argument");
27+
NODE_API_CALL(env, napi_typeof(env, cb, &value_type));
28+
NODE_API_ASSERT(env, value_type == napi_function, "argument must be function");
29+
NODE_API_CALL(env, napi_add_finalizer(env, cb, ref, Finalize, NULL, ref));
30+
return cb;
31+
}
32+
33+
NAPI_MODULE_INIT() {
34+
napi_property_descriptor properties[] = {
35+
DECLARE_NODE_API_PROPERTY("createRef", CreateRef),
36+
};
37+
38+
NODE_API_CALL(
39+
env,
40+
napi_define_properties(
41+
env, exports, sizeof(properties) / sizeof(*properties), properties));
42+
43+
return exports;
44+
}

0 commit comments

Comments
 (0)