From fabdb5afbb5bcca278af51e7d16ca291974140f8 Mon Sep 17 00:00:00 2001 From: Gabriel Schulhof Date: Thu, 7 Sep 2023 00:54:32 -0700 Subject: [PATCH] node-api: segregate nogc APIs from rest via type system We define a new type called `node_api_nogc_env` as the `const` version of `napi_env` and `node_api_nogc_finalize` as a variant of `napi_finalize` that accepts a `node_api_nogc_env` as its first argument. We then modify those APIs which do not affect GC state as accepting a `node_api_nogc_env`. APIs accepting finalizer callbacks are modified to accept `node_api_nogc_finalize` callbacks. Thus, the only way to attach a `napi_finalize` callback, wherein Node-APIs affecting GC state may be called is to call `node_api_post_finalizer` from a `node_api_nogc_finalize` callback. In keeping with the process of introducing new Node-APIs, this feature is guarded by `NAPI_EXPERIMENTAL`. Since this feature modifies APIs already marked as stable, it is additionally guared by `NODE_API_EXPERIMENTAL_NOGC_ENV`, so as to provide a further buffer to adoption. Nevertheless, both guards must be removed upon releasing a new version of Node-API. --- doc/api/n-api.md | 142 +++++++++++++----- doc/contributing/adding-new-napi-api.md | 9 ++ doc/contributing/releases-node-api.md | 17 ++- src/js_native_api.h | 44 +++--- src/js_native_api_types.h | 34 +++++ src/js_native_api_v8.cc | 84 ++++++----- src/js_native_api_v8.h | 8 +- src/node_api.cc | 45 +++--- src/node_api.h | 32 ++-- test/js-native-api/common.h | 65 ++++++-- .../test_cannot_run_js/binding.gyp | 2 +- .../test_cannot_run_js/test_cannot_run_js.c | 11 +- test/js-native-api/test_finalizer/binding.gyp | 2 +- .../test_finalizer/test_finalizer.c | 20 +-- test/js-native-api/test_string/binding.gyp | 3 + test/js-native-api/test_string/test_string.c | 5 +- test/node-api/test_async/test_async.c | 4 +- test/node-api/test_callback_scope/binding.c | 4 +- test/node-api/test_env_teardown_gc/binding.c | 2 +- test/node-api/test_general/test_general.c | 4 +- .../binding.gyp | 2 +- .../test_reference_by_node_api_version.c | 8 +- .../test_threadsafe_function/binding.gyp | 2 +- .../test_uncaught_exception.c | 21 ++- 24 files changed, 390 insertions(+), 180 deletions(-) diff --git a/doc/api/n-api.md b/doc/api/n-api.md index 15a6a2932cdfaf..fe1f2765d236a7 100644 --- a/doc/api/n-api.md +++ b/doc/api/n-api.md @@ -78,7 +78,7 @@ it still gets the benefits of the ABI stability provided by the C API. When using `node-addon-api` instead of the C APIs, start with the API [docs][] for `node-addon-api`. -The [Node-API Resource](https://nodejs.github.io/node-addon-examples/) offers +The [Node-API Resource](https://nodejs.github.io/node-addon-examples/) offers an excellent orientation and tips for developers just getting started with Node-API and `node-addon-api`. Additional media resources can be found on the [Node-API Media][] page. @@ -175,7 +175,8 @@ developers have run into limitations in node-gyp. [CMake.js][] is an alternative build system based on [CMake][]. CMake.js is a good choice for projects that already use CMake or for -developers affected by limitations in node-gyp. +developers affected by limitations in node-gyp. [`build_with_cmake`][] is an +example of a CMake-based native addon project. ### Uploading precompiled binaries @@ -237,6 +238,18 @@ Some of the Node-API surface is experimental and requires explicit opt-in: In this case the entire API surface, including any experimental APIs, will be available to the module code. +Occasionally, experimental features are introduced that affect already-released +and stable APIs. These features are guarded by an additional opt-in: + +```c +#define NAPI_EXPERIMENTAL +#define NODE_API_EXPERIMENTAL_ +#include +``` + +where `` is the name of an experimental feature that affects both +experimental and stable APIs. + ## Node-API version matrix Up until version 9, Node-API versions were additive and versioned @@ -419,7 +432,7 @@ napi_value create_addon(napi_env env) { #include #include "addon.h" -NAPI_MODULE_INIT() { +NAPI_MODULE_INIT(/* napi_env env, napi_value exports */) { // This function body is expected to return a `napi_value`. // The variables `napi_env env` and `napi_value exports` may be used within // the body, as they are provided by the definition of `NAPI_MODULE_INIT()`. @@ -464,7 +477,7 @@ napiVersion: 6 --> ```c -napi_status napi_set_instance_data(napi_env env, +napi_status napi_set_instance_data(node_api_nogc_env env, void* data, napi_finalize finalize_cb, void* finalize_hint); @@ -496,7 +509,7 @@ napiVersion: 6 --> ```c -napi_status napi_get_instance_data(napi_env env, +napi_status napi_get_instance_data(node_api_nogc_env env, void** data); ``` @@ -598,6 +611,22 @@ when an instance of a native addon is unloaded. Notification of this event is delivered through the callbacks given to [`napi_add_env_cleanup_hook`][] and [`napi_set_instance_data`][]. +### `node_api_nogc_env` + +> Stability: 1 - Experimental + +This variant of `napi_env` is passed to synchronous finalizers +([`node_api_nogc_finalize`][]). There is a subset of Node-APIs which accept +a parameter of type `node_api_nogc_env` as their first argument. These APIs do +not access the state of the JavaScript engine and are thus safe to call from +synchronous finalizers. Passing a parameter of type `napi_env` to these APIs is +allowed, however, passing a parameter of type `node_api_nogc_env` to APIs that +access the JavaScript engine state is not allowed. Attempting to do so without +a cast will produce a compiler warning or an error when add-ons are compiled +with flags which cause them to emit warnings and/or errors when incorrect +pointer types are passed into a function. Calling such APIs from a synchronous +finalizer will ultimately result in the termination of the application. + ### `napi_value` This is an opaque pointer that is used to represent a JavaScript value. @@ -762,32 +791,36 @@ typedef napi_value (*napi_callback)(napi_env, napi_callback_info); Unless for reasons discussed in [Object Lifetime Management][], creating a handle and/or callback scope inside a `napi_callback` is not necessary. -#### `napi_finalize` +#### `node_api_nogc_finalize` +> Stability: 1 - Experimental + Function pointer type for add-on provided functions that allow the user to be notified when externally-owned data is ready to be cleaned up because the -object with which it was associated with has been garbage-collected. The user -must provide a function satisfying the following signature which would get -called upon the object's collection. Currently, `napi_finalize` can be used for +object it was associated with has been garbage-collected. The user must provide +a function satisfying the following signature which would get called upon the +object's collection. Currently, `node_api_nogc_finalize` can be used for finding out when objects that have external data are collected. ```c -typedef void (*napi_finalize)(napi_env env, - void* finalize_data, - void* finalize_hint); +typedef void (*node_api_nogc_finalize)(node_api_nogc_env env, + void* finalize_data, + void* finalize_hint); ``` Unless for reasons discussed in [Object Lifetime Management][], creating a handle and/or callback scope inside the function body is not necessary. Since these functions may be called while the JavaScript engine is in a state -where it cannot execute JavaScript code, some Node-API calls may return -`napi_pending_exception` even when there is no exception pending. +where it cannot execute JavaScript code, only Node-APIs which accept a +`node_api_nogc_env` as their first parameter may be called. +[`node_api_post_finalizer`][] can be used to schedule Node-API calls that +require access to the JavaScript engine's state to run after the current +garbage collection cycle has completed. In the case of [`node_api_create_external_string_latin1`][] and [`node_api_create_external_string_utf16`][] the `env` parameter may be null, @@ -796,11 +829,40 @@ shutdown. Change History: -* experimental (`NAPI_EXPERIMENTAL` is defined): +* experimental (`NAPI_EXPERIMENTAL` and `NODE_API_EXPERIMENTAL_NOGC_ENV` are + defined): - Node-API calls made from a finalizer will return `napi_cannot_run_js` when - the JavaScript engine is unable to execute JavaScript, and will return - `napi_exception_pending` if there is a pending exception. + Only Node-API calls that accept a `node_api_nogc_env` as their first + parameter may be called, otherwise the application will be terminated with an + appropriate error message. + +#### `napi_finalize` + + + +Function pointer type for add-on provided function that allow the user to +schedule a group of calls to Node-APIs in response to a garbage collection +event, after the garbage collection cycle has completed. These function +pointers can be used with [`node_api_post_finalizer`][] and +[`napi_set_instance_data`][]. + +```c +typedef void (*napi_finalize)(napi_env env, + void* finalize_data, + void* finalize_hint); +``` + +Change History: + +* experimental (`NAPI_EXPERIMENTAL` and `NODE_API_EXPERIMENTAL_NOGC_ENV` are + defined): + + A function of this type may no longer be used as a finalizer, except with + [`node_api_post_finalizer`][] and [`napi_set_instance_data`][]. + [`node_api_nogc_finalize`][] must be used instead. #### `napi_async_execute_callback` @@ -1002,7 +1064,7 @@ napiVersion: 1 ```c napi_status -napi_get_last_error_info(napi_env env, +napi_get_last_error_info(node_api_nogc_env env, const napi_extended_error_info** result); ``` @@ -1821,7 +1883,7 @@ napiVersion: 3 --> ```c -NODE_EXTERN napi_status napi_add_env_cleanup_hook(napi_env env, +NODE_EXTERN napi_status napi_add_env_cleanup_hook(node_api_nogc_env env, napi_cleanup_hook fun, void* arg); ``` @@ -1851,7 +1913,7 @@ napiVersion: 3 --> ```c -NAPI_EXTERN napi_status napi_remove_env_cleanup_hook(napi_env env, +NAPI_EXTERN napi_status napi_remove_env_cleanup_hook(node_api_nogc_env env, void (*fun)(void* arg), void* arg); ``` @@ -1880,7 +1942,7 @@ changes: ```c NAPI_EXTERN napi_status napi_add_async_cleanup_hook( - napi_env env, + node_api_nogc_env env, napi_async_cleanup_hook hook, void* arg, napi_async_cleanup_hook_handle* remove_handle); @@ -2038,7 +2100,7 @@ You can also use the `NAPI_MODULE_INIT` macro, which acts as a shorthand for `NAPI_MODULE` and defining an `Init` function: ```c -NAPI_MODULE_INIT() { +NAPI_MODULE_INIT(/* napi_env env, napi_value exports */) { napi_value answer; napi_status result; @@ -2052,6 +2114,9 @@ NAPI_MODULE_INIT() { } ``` +The parameters `env` and `exports` are provided to the body of the +`NAPI_MODULE_INIT` macro. + All Node-API addons are context-aware, meaning they may be loaded multiple times. There are a few design considerations when declaring such a module. The documentation on [context-aware addons][] provides more details. @@ -5420,7 +5485,7 @@ napiVersion: 5 napi_status napi_add_finalizer(napi_env env, napi_value js_object, void* finalize_data, - napi_finalize finalize_cb, + node_api_nogc_finalize finalize_cb, void* finalize_hint, napi_ref* result); ``` @@ -5458,7 +5523,7 @@ added: v21.0.0 > Stability: 1 - Experimental ```c -napi_status node_api_post_finalizer(napi_env env, +napi_status node_api_post_finalizer(node_api_nogc_env env, napi_finalize finalize_cb, void* finalize_data, void* finalize_hint); @@ -5528,7 +5593,7 @@ Once created the async worker can be queued for execution using the [`napi_queue_async_work`][] function: ```c -napi_status napi_queue_async_work(napi_env env, +napi_status napi_queue_async_work(node_api_nogc_env env, napi_async_work work); ``` @@ -5620,7 +5685,7 @@ napiVersion: 1 --> ```c -napi_status napi_queue_async_work(napi_env env, +napi_status napi_queue_async_work(node_api_nogc_env env, napi_async_work work); ``` @@ -5641,7 +5706,7 @@ napiVersion: 1 --> ```c -napi_status napi_cancel_async_work(napi_env env, +napi_status napi_cancel_async_work(node_api_nogc_env env, napi_async_work work); ``` @@ -5845,7 +5910,7 @@ typedef struct { const char* release; } napi_node_version; -napi_status napi_get_node_version(napi_env env, +napi_status napi_get_node_version(node_api_nogc_env env, const napi_node_version** version); ``` @@ -5868,7 +5933,7 @@ napiVersion: 1 --> ```c -napi_status napi_get_version(napi_env env, +napi_status napi_get_version(node_api_nogc_env env, uint32_t* result); ``` @@ -5901,7 +5966,7 @@ napiVersion: 1 --> ```c -NAPI_EXTERN napi_status napi_adjust_external_memory(napi_env env, +NAPI_EXTERN napi_status napi_adjust_external_memory(node_api_nogc_env env, int64_t change_in_bytes, int64_t* result); ``` @@ -6118,7 +6183,7 @@ napiVersion: 2 --> ```c -NAPI_EXTERN napi_status napi_get_uv_event_loop(napi_env env, +NAPI_EXTERN napi_status napi_get_uv_event_loop(node_api_nogc_env env, struct uv_loop_s** loop); ``` @@ -6432,7 +6497,7 @@ napiVersion: 4 ```c NAPI_EXTERN napi_status -napi_ref_threadsafe_function(napi_env env, napi_threadsafe_function func); +napi_ref_threadsafe_function(node_api_nogc_env env, napi_threadsafe_function func); ``` * `[in] env`: The environment that the API is invoked under. @@ -6458,7 +6523,7 @@ napiVersion: 4 ```c NAPI_EXTERN napi_status -napi_unref_threadsafe_function(napi_env env, napi_threadsafe_function func); +napi_unref_threadsafe_function(node_api_nogc_env env, napi_threadsafe_function func); ``` * `[in] env`: The environment that the API is invoked under. @@ -6484,7 +6549,7 @@ napiVersion: 9 ```c NAPI_EXTERN napi_status -node_api_get_module_file_name(napi_env env, const char** result); +node_api_get_module_file_name(node_api_nogc_env env, const char** result); ``` @@ -6548,6 +6613,7 @@ the add-on's file name during loading. [`Number.MIN_SAFE_INTEGER`]: https://tc39.github.io/ecma262/#sec-number.min_safe_integer [`Worker`]: worker_threads.md#class-worker [`async_hooks.executionAsyncResource()`]: async_hooks.md#async_hooksexecutionasyncresource +[`build_with_cmake`]: https://github.com/nodejs/node-addon-examples/tree/main/build_with_cmake [`global`]: globals.md#global [`init` hooks]: async_hooks.md#initasyncid-type-triggerasyncid-resource [`napi_add_async_cleanup_hook`]: #napi_add_async_cleanup_hook @@ -6611,6 +6677,8 @@ the add-on's file name during loading. [`node_api_create_external_string_latin1`]: #node_api_create_external_string_latin1 [`node_api_create_external_string_utf16`]: #node_api_create_external_string_utf16 [`node_api_create_syntax_error`]: #node_api_create_syntax_error +[`node_api_nogc_finalize`]: #node_api_nogc_finalize +[`node_api_post_finalizer`]: #node_api_post_finalizer [`node_api_throw_syntax_error`]: #node_api_throw_syntax_error [`process.release`]: process.md#processrelease [`uv_ref`]: https://docs.libuv.org/en/v1.x/handle.html#c.uv_ref diff --git a/doc/contributing/adding-new-napi-api.md b/doc/contributing/adding-new-napi-api.md index 8d2233bc30f690..2d3def790ef5a7 100644 --- a/doc/contributing/adding-new-napi-api.md +++ b/doc/contributing/adding-new-napi-api.md @@ -47,3 +47,12 @@ Node-API. to the decision to take an API out of experimental status. * The API **must** be implemented in a Node.js implementation with an alternate VM. + +Since the adoption of the policy whereby moving to a later version of Node-API +from an earlier version may entail rework of existing code, it is possible to +introduce modifications to already-released Node-APIs, as long as the +modifications affect neither the ABI nor the API of earlier versions. Such +modifications **must** be guarded, in addition to the above-mentioned +compile-time flag, by a further compile-time flag whose name reflects the +nature of the modification being made to existing APIs. This provides a buffer +to adoption above and beyond the one provided by the initial compile-time flag. diff --git a/doc/contributing/releases-node-api.md b/doc/contributing/releases-node-api.md index f2277b228ec0d6..43f05118b481a3 100644 --- a/doc/contributing/releases-node-api.md +++ b/doc/contributing/releases-node-api.md @@ -84,7 +84,11 @@ define guards on the declaration of the new Node-API. Check for these guards with: ```bash -grep NAPI_EXPERIMENTAL src/js_native_api{_types,}.h src/node_api{_types,}.h +grep \ + -E \ + 'N(ODE_)?API_EXPERIMENTAL' \ + src/js_native_api{_types,}.h \ + src/node_api{_types,}.h ``` and update the define version guards with the release version: @@ -100,6 +104,9 @@ and update the define version guards with the release version: + #endif // NAPI_VERSION >= 10 ``` +Remove any additional `NODE_API_EXPERIMENTAL_*` guards along with +`NAPI_EXPERIMENTAL`. + Also, update the Node-API version value of the `napi_get_version` test in `test/js-native-api/test_general/test.js` with the release version `x`: @@ -128,7 +135,11 @@ commits should already include `NAPI_EXPERIMENTAL` definition for the tests. Check for these definitions with: ```bash -grep NAPI_EXPERIMENTAL test/node-api/*/{*.{h,c},binding.gyp} test/js-native-api/*/{*.{h,c},binding.gyp} +grep \ + -E \ + 'N(ODE_)?API_EXPERIMENTAL' \ + test/node-api/*/{*.{h,c},binding.gyp} \ + test/js-native-api/*/{*.{h,c},binding.gyp} ``` and substitute the `NAPI_EXPERIMENTAL` with the release version @@ -139,6 +150,8 @@ and substitute the `NAPI_EXPERIMENTAL` with the release version + #define NAPI_VERSION 10 ``` +Remove any `NODE_API_EXPERIMENTAL_*` flags. + #### Step 4. Update document If this release includes new Node-APIs that were first released in this diff --git a/src/js_native_api.h b/src/js_native_api.h index d665052b947552..24847bc769494a 100644 --- a/src/js_native_api.h +++ b/src/js_native_api.h @@ -49,8 +49,8 @@ EXTERN_C_START -NAPI_EXTERN napi_status NAPI_CDECL -napi_get_last_error_info(napi_env env, const napi_extended_error_info** result); +NAPI_EXTERN napi_status NAPI_CDECL napi_get_last_error_info( + node_api_nogc_env env, const napi_extended_error_info** result); // Getters for defined singletons NAPI_EXTERN napi_status NAPI_CDECL napi_get_undefined(napi_env env, @@ -97,7 +97,7 @@ NAPI_EXTERN napi_status NAPI_CDECL node_api_create_external_string_latin1(napi_env env, char* str, size_t length, - napi_finalize finalize_callback, + node_api_nogc_finalize finalize_callback, void* finalize_hint, napi_value* result, bool* copied); @@ -105,7 +105,7 @@ NAPI_EXTERN napi_status NAPI_CDECL node_api_create_external_string_utf16(napi_env env, char16_t* str, size_t length, - napi_finalize finalize_callback, + node_api_nogc_finalize finalize_callback, void* finalize_hint, napi_value* result, bool* copied); @@ -289,7 +289,7 @@ NAPI_EXTERN napi_status NAPI_CDECL napi_instanceof(napi_env env, // Gets all callback info in a single call. (Ugly, but faster.) NAPI_EXTERN napi_status NAPI_CDECL napi_get_cb_info( - napi_env env, // [in] NAPI environment handle + napi_env env, // [in] Node-API environment handle napi_callback_info cbinfo, // [in] Opaque callback-info handle size_t* argc, // [in-out] Specifies the size of the provided argv array // and receives the actual count of args. @@ -313,7 +313,7 @@ napi_define_class(napi_env env, NAPI_EXTERN napi_status NAPI_CDECL napi_wrap(napi_env env, napi_value js_object, void* native_object, - napi_finalize finalize_cb, + node_api_nogc_finalize finalize_cb, void* finalize_hint, napi_ref* result); NAPI_EXTERN napi_status NAPI_CDECL napi_unwrap(napi_env env, @@ -325,7 +325,7 @@ NAPI_EXTERN napi_status NAPI_CDECL napi_remove_wrap(napi_env env, NAPI_EXTERN napi_status NAPI_CDECL napi_create_external(napi_env env, void* data, - napi_finalize finalize_cb, + node_api_nogc_finalize finalize_cb, void* finalize_hint, napi_value* result); NAPI_EXTERN napi_status NAPI_CDECL napi_get_value_external(napi_env env, @@ -424,7 +424,7 @@ NAPI_EXTERN napi_status NAPI_CDECL napi_create_external_arraybuffer(napi_env env, void* external_data, size_t byte_length, - napi_finalize finalize_cb, + node_api_nogc_finalize finalize_cb, void* finalize_hint, napi_value* result); #endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED @@ -466,7 +466,7 @@ napi_get_dataview_info(napi_env env, size_t* byte_offset); // version management -NAPI_EXTERN napi_status NAPI_CDECL napi_get_version(napi_env env, +NAPI_EXTERN napi_status NAPI_CDECL napi_get_version(node_api_nogc_env env, uint32_t* result); // Promises @@ -490,7 +490,7 @@ NAPI_EXTERN napi_status NAPI_CDECL napi_run_script(napi_env env, // Memory management NAPI_EXTERN napi_status NAPI_CDECL napi_adjust_external_memory( - napi_env env, int64_t change_in_bytes, int64_t* adjusted_value); + node_api_nogc_env env, int64_t change_in_bytes, int64_t* adjusted_value); #if NAPI_VERSION >= 5 @@ -508,19 +508,20 @@ NAPI_EXTERN napi_status NAPI_CDECL napi_get_date_value(napi_env env, double* result); // Add finalizer for pointer -NAPI_EXTERN napi_status NAPI_CDECL napi_add_finalizer(napi_env env, - napi_value js_object, - void* finalize_data, - napi_finalize finalize_cb, - void* finalize_hint, - napi_ref* result); +NAPI_EXTERN napi_status NAPI_CDECL +napi_add_finalizer(napi_env env, + napi_value js_object, + void* finalize_data, + node_api_nogc_finalize finalize_cb, + void* finalize_hint, + napi_ref* result); #endif // NAPI_VERSION >= 5 #ifdef NAPI_EXPERIMENTAL NAPI_EXTERN napi_status NAPI_CDECL -node_api_post_finalizer(napi_env env, +node_api_post_finalizer(node_api_nogc_env env, napi_finalize finalize_cb, void* finalize_data, void* finalize_hint); @@ -564,10 +565,13 @@ napi_get_all_property_names(napi_env env, napi_value* result); // Instance data -NAPI_EXTERN napi_status NAPI_CDECL napi_set_instance_data( - napi_env env, void* data, napi_finalize finalize_cb, void* finalize_hint); +NAPI_EXTERN napi_status NAPI_CDECL +napi_set_instance_data(node_api_nogc_env env, + void* data, + napi_finalize finalize_cb, + void* finalize_hint); -NAPI_EXTERN napi_status NAPI_CDECL napi_get_instance_data(napi_env env, +NAPI_EXTERN napi_status NAPI_CDECL napi_get_instance_data(node_api_nogc_env env, void** data); #endif // NAPI_VERSION >= 6 diff --git a/src/js_native_api_types.h b/src/js_native_api_types.h index 005382f173fee9..d1e523a31fbf46 100644 --- a/src/js_native_api_types.h +++ b/src/js_native_api_types.h @@ -22,6 +22,33 @@ typedef uint16_t char16_t; // JSVM API types are all opaque pointers for ABI stability // typedef undefined structs instead of void* for compile time type safety typedef struct napi_env__* napi_env; + +// We need to mark APIs which can be called during garbage collection (GC), +// meaning that they do not affect the state of the JS engine, and can +// therefore be called synchronously from a finalizer that itself runs +// synchronously during GC. Such APIs can receive either a `napi_env` or a +// `node_api_nogc_env` as their first parameter, because we should be able to +// also call them during normal, non-garbage-collecting operations, whereas +// APIs that affect the state of the JS engine can only receive a `napi_env` as +// their first parameter, because we must not call them during GC. In lieu of +// inheritance, we use the properties of the const qualifier to accomplish +// this, because both a const and a non-const value can be passed to an API +// expecting a const value, but only a non-const value can be passed to an API +// expecting a non-const value. +// +// In conjunction with appropriate CFLAGS to warn us if we're passing a const +// (nogc) environment into an API that expects a non-const environment, and the +// definition of nogc finalizer function pointer types below, which receive a +// nogc environment as their first parameter, and can thus only call nogc APIs +// (unless the user explicitly casts the environment), we achieve the ability +// to ensure at compile time that we do not call APIs that affect the state of +// the JS engine from a synchronous (nogc) finalizer. +#if defined(NAPI_EXPERIMENTAL) && defined(NODE_API_EXPERIMENTAL_NOGC_ENV) +typedef const struct napi_env__* node_api_nogc_env; +#else +typedef struct napi_env__* node_api_nogc_env; +#endif // NAPI_EXPERIMENTAL && NODE_API_EXPERIMENTAL_NOGC_ENV + typedef struct napi_value__* napi_value; typedef struct napi_ref__* napi_ref; typedef struct napi_handle_scope__* napi_handle_scope; @@ -115,6 +142,13 @@ typedef napi_value(NAPI_CDECL* napi_callback)(napi_env env, typedef void(NAPI_CDECL* napi_finalize)(napi_env env, void* finalize_data, void* finalize_hint); +#if defined(NAPI_EXPERIMENTAL) && defined(NODE_API_EXPERIMENTAL_NOGC_ENV) +typedef void(NAPI_CDECL* node_api_nogc_finalize)(node_api_nogc_env env, + void* finalize_data, + void* finalize_hint); +#else +typedef napi_finalize node_api_nogc_finalize; +#endif // NAPI_EXPERIMENTAL && NODE_API_EXPERIMENTAL_NOGC_ENV typedef struct { // One of utf8name or name should be NULL. diff --git a/src/js_native_api_v8.cc b/src/js_native_api_v8.cc index 54d0c2a70b215b..2fe6989e9f1980 100644 --- a/src/js_native_api_v8.cc +++ b/src/js_native_api_v8.cc @@ -856,7 +856,8 @@ static const char* error_messages[] = { }; napi_status NAPI_CDECL napi_get_last_error_info( - napi_env env, const napi_extended_error_info** result) { + node_api_nogc_env nogc_env, const napi_extended_error_info** result) { + napi_env env = const_cast(nogc_env); CHECK_ENV(env); CHECK_ARG(env, result); @@ -1597,14 +1598,16 @@ napi_status NAPI_CDECL napi_create_string_utf16(napi_env env, }); } -napi_status NAPI_CDECL -node_api_create_external_string_latin1(napi_env env, - char* str, - size_t length, - napi_finalize finalize_callback, - void* finalize_hint, - napi_value* result, - bool* copied) { +napi_status NAPI_CDECL node_api_create_external_string_latin1( + napi_env env, + char* str, + size_t length, + node_api_nogc_finalize nogc_finalize_callback, + void* finalize_hint, + napi_value* result, + bool* copied) { + napi_finalize finalize_callback = + reinterpret_cast(nogc_finalize_callback); return v8impl::NewExternalString( env, str, @@ -1624,14 +1627,16 @@ node_api_create_external_string_latin1(napi_env env, }); } -napi_status NAPI_CDECL -node_api_create_external_string_utf16(napi_env env, - char16_t* str, - size_t length, - napi_finalize finalize_callback, - void* finalize_hint, - napi_value* result, - bool* copied) { +napi_status NAPI_CDECL node_api_create_external_string_utf16( + napi_env env, + char16_t* str, + size_t length, + node_api_nogc_finalize nogc_finalize_callback, + void* finalize_hint, + napi_value* result, + bool* copied) { + napi_finalize finalize_callback = + reinterpret_cast(nogc_finalize_callback); return v8impl::NewExternalString( env, str, @@ -2493,9 +2498,10 @@ GEN_COERCE_FUNCTION(STRING, String, string) napi_status NAPI_CDECL napi_wrap(napi_env env, napi_value js_object, void* native_object, - napi_finalize finalize_cb, + node_api_nogc_finalize nogc_finalize_cb, void* finalize_hint, napi_ref* result) { + napi_finalize finalize_cb = reinterpret_cast(nogc_finalize_cb); return v8impl::Wrap( env, js_object, native_object, finalize_cb, finalize_hint, result); } @@ -2512,11 +2518,13 @@ napi_status NAPI_CDECL napi_remove_wrap(napi_env env, return v8impl::Unwrap(env, obj, result, v8impl::RemoveWrap); } -napi_status NAPI_CDECL napi_create_external(napi_env env, - void* data, - napi_finalize finalize_cb, - void* finalize_hint, - napi_value* result) { +napi_status NAPI_CDECL +napi_create_external(napi_env env, + void* data, + node_api_nogc_finalize nogc_finalize_cb, + void* finalize_hint, + napi_value* result) { + napi_finalize finalize_cb = reinterpret_cast(nogc_finalize_cb); NAPI_PREAMBLE(env); CHECK_ARG(env, result); @@ -2935,7 +2943,7 @@ napi_status NAPI_CDECL napi_create_external_arraybuffer(napi_env env, void* external_data, size_t byte_length, - napi_finalize finalize_cb, + node_api_nogc_finalize finalize_cb, void* finalize_hint, napi_value* result) { // The API contract here is that the cleanup function runs on the JS thread, @@ -3200,7 +3208,8 @@ napi_status NAPI_CDECL napi_get_dataview_info(napi_env env, return napi_clear_last_error(env); } -napi_status NAPI_CDECL napi_get_version(napi_env env, uint32_t* result) { +napi_status NAPI_CDECL napi_get_version(node_api_nogc_env env, + uint32_t* result) { CHECK_ENV(env); CHECK_ARG(env, result); *result = NODE_API_SUPPORTED_VERSION_MAX; @@ -3317,14 +3326,16 @@ napi_status NAPI_CDECL napi_run_script(napi_env env, return GET_RETURN_STATUS(env); } -napi_status NAPI_CDECL napi_add_finalizer(napi_env env, - napi_value js_object, - void* finalize_data, - napi_finalize finalize_cb, - void* finalize_hint, - napi_ref* result) { +napi_status NAPI_CDECL +napi_add_finalizer(napi_env env, + napi_value js_object, + void* finalize_data, + node_api_nogc_finalize nogc_finalize_cb, + void* finalize_hint, + napi_ref* result) { // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw // JS exceptions. + napi_finalize finalize_cb = reinterpret_cast(nogc_finalize_cb); CHECK_ENV_NOT_IN_GC(env); CHECK_ARG(env, js_object); CHECK_ARG(env, finalize_cb); @@ -3348,10 +3359,11 @@ napi_status NAPI_CDECL napi_add_finalizer(napi_env env, #ifdef NAPI_EXPERIMENTAL -napi_status NAPI_CDECL node_api_post_finalizer(napi_env env, +napi_status NAPI_CDECL node_api_post_finalizer(node_api_nogc_env nogc_env, napi_finalize finalize_cb, void* finalize_data, void* finalize_hint) { + napi_env env = const_cast(nogc_env); CHECK_ENV(env); env->EnqueueFinalizer(v8impl::TrackedFinalizer::New( env, finalize_cb, finalize_data, finalize_hint)); @@ -3360,7 +3372,7 @@ napi_status NAPI_CDECL node_api_post_finalizer(napi_env env, #endif -napi_status NAPI_CDECL napi_adjust_external_memory(napi_env env, +napi_status NAPI_CDECL napi_adjust_external_memory(node_api_nogc_env env, int64_t change_in_bytes, int64_t* adjusted_value) { CHECK_ENV(env); @@ -3372,10 +3384,11 @@ napi_status NAPI_CDECL napi_adjust_external_memory(napi_env env, return napi_clear_last_error(env); } -napi_status NAPI_CDECL napi_set_instance_data(napi_env env, +napi_status NAPI_CDECL napi_set_instance_data(node_api_nogc_env nogc_env, void* data, napi_finalize finalize_cb, void* finalize_hint) { + napi_env env = const_cast(nogc_env); CHECK_ENV(env); v8impl::RefBase* old_data = static_cast(env->instance_data); @@ -3391,7 +3404,8 @@ napi_status NAPI_CDECL napi_set_instance_data(napi_env env, return napi_clear_last_error(env); } -napi_status NAPI_CDECL napi_get_instance_data(napi_env env, void** data) { +napi_status NAPI_CDECL napi_get_instance_data(node_api_nogc_env env, + void** data) { CHECK_ENV(env); CHECK_ARG(env, data); diff --git a/src/js_native_api_v8.h b/src/js_native_api_v8.h index bdb29490dd9c4e..1974c3f6873ef7 100644 --- a/src/js_native_api_v8.h +++ b/src/js_native_api_v8.h @@ -4,7 +4,7 @@ #include "js_native_api_types.h" #include "js_native_api_v8_internals.h" -inline napi_status napi_clear_last_error(napi_env env); +inline napi_status napi_clear_last_error(node_api_nogc_env env); namespace v8impl { @@ -172,7 +172,8 @@ struct napi_env__ { virtual ~napi_env__() = default; }; -inline napi_status napi_clear_last_error(napi_env env) { +inline napi_status napi_clear_last_error(node_api_nogc_env nogc_env) { + napi_env env = const_cast(nogc_env); env->last_error.error_code = napi_ok; env->last_error.engine_error_code = 0; env->last_error.engine_reserved = nullptr; @@ -180,10 +181,11 @@ inline napi_status napi_clear_last_error(napi_env env) { return napi_ok; } -inline napi_status napi_set_last_error(napi_env env, +inline napi_status napi_set_last_error(node_api_nogc_env nogc_env, napi_status error_code, uint32_t engine_error_code = 0, void* engine_reserved = nullptr) { + napi_env env = const_cast(nogc_env); env->last_error.error_code = error_code; env->last_error.engine_error_code = engine_error_code; env->last_error.engine_reserved = engine_reserved; diff --git a/src/node_api.cc b/src/node_api.cc index 1a6cdcb8b1de9a..4f2d65a595f61c 100644 --- a/src/node_api.cc +++ b/src/node_api.cc @@ -765,7 +765,7 @@ void NAPI_CDECL napi_module_register(napi_module* mod) { node::node_module_register(nm); } -napi_status NAPI_CDECL napi_add_env_cleanup_hook(napi_env env, +napi_status NAPI_CDECL napi_add_env_cleanup_hook(node_api_nogc_env env, napi_cleanup_hook fun, void* arg) { CHECK_ENV(env); @@ -776,7 +776,7 @@ napi_status NAPI_CDECL napi_add_env_cleanup_hook(napi_env env, return napi_ok; } -napi_status NAPI_CDECL napi_remove_env_cleanup_hook(napi_env env, +napi_status NAPI_CDECL napi_remove_env_cleanup_hook(node_api_nogc_env env, napi_cleanup_hook fun, void* arg) { CHECK_ENV(env); @@ -823,7 +823,7 @@ struct napi_async_cleanup_hook_handle__ { }; napi_status NAPI_CDECL -napi_add_async_cleanup_hook(napi_env env, +napi_add_async_cleanup_hook(node_api_nogc_env env, napi_async_cleanup_hook hook, void* arg, napi_async_cleanup_hook_handle* remove_handle) { @@ -1037,12 +1037,14 @@ napi_status NAPI_CDECL napi_create_buffer(napi_env env, return GET_RETURN_STATUS(env); } -napi_status NAPI_CDECL napi_create_external_buffer(napi_env env, - size_t length, - void* data, - napi_finalize finalize_cb, - void* finalize_hint, - napi_value* result) { +napi_status NAPI_CDECL +napi_create_external_buffer(napi_env env, + size_t length, + void* data, + node_api_nogc_finalize nogc_finalize_cb, + void* finalize_hint, + napi_value* result) { + napi_finalize finalize_cb = reinterpret_cast(nogc_finalize_cb); NAPI_PREAMBLE(env); CHECK_ARG(env, result); @@ -1126,7 +1128,7 @@ napi_status NAPI_CDECL napi_get_buffer_info(napi_env env, return napi_clear_last_error(env); } -napi_status NAPI_CDECL napi_get_node_version(napi_env env, +napi_status NAPI_CDECL napi_get_node_version(node_api_nogc_env env, const napi_node_version** result) { CHECK_ENV(env); CHECK_ARG(env, result); @@ -1270,14 +1272,16 @@ napi_status NAPI_CDECL napi_delete_async_work(napi_env env, return napi_clear_last_error(env); } -napi_status NAPI_CDECL napi_get_uv_event_loop(napi_env env, uv_loop_t** loop) { +napi_status NAPI_CDECL napi_get_uv_event_loop(node_api_nogc_env nogc_env, + uv_loop_t** loop) { + napi_env env = const_cast(nogc_env); CHECK_ENV(env); CHECK_ARG(env, loop); *loop = reinterpret_cast(env)->node_env()->event_loop(); return napi_clear_last_error(env); } -napi_status NAPI_CDECL napi_queue_async_work(napi_env env, +napi_status NAPI_CDECL napi_queue_async_work(node_api_nogc_env env, napi_async_work work) { CHECK_ENV(env); CHECK_ARG(env, work); @@ -1292,7 +1296,7 @@ napi_status NAPI_CDECL napi_queue_async_work(napi_env env, return napi_clear_last_error(env); } -napi_status NAPI_CDECL napi_cancel_async_work(napi_env env, +napi_status NAPI_CDECL napi_cancel_async_work(node_api_nogc_env env, napi_async_work work) { CHECK_ENV(env); CHECK_ARG(env, work); @@ -1312,10 +1316,12 @@ napi_create_threadsafe_function(napi_env env, size_t max_queue_size, size_t initial_thread_count, void* thread_finalize_data, - napi_finalize thread_finalize_cb, + node_api_nogc_finalize nogc_thread_finalize_cb, void* context, napi_threadsafe_function_call_js call_js_cb, napi_threadsafe_function* result) { + napi_finalize thread_finalize_cb = + reinterpret_cast(nogc_thread_finalize_cb); CHECK_ENV_NOT_IN_GC(env); CHECK_ARG(env, async_resource_name); RETURN_STATUS_IF_FALSE(env, initial_thread_count > 0, napi_invalid_arg); @@ -1397,20 +1403,21 @@ napi_status NAPI_CDECL napi_release_threadsafe_function( return reinterpret_cast(func)->Release(mode); } -napi_status NAPI_CDECL -napi_unref_threadsafe_function(napi_env env, napi_threadsafe_function func) { +napi_status NAPI_CDECL napi_unref_threadsafe_function( + node_api_nogc_env env, napi_threadsafe_function func) { CHECK_NOT_NULL(func); return reinterpret_cast(func)->Unref(); } -napi_status NAPI_CDECL -napi_ref_threadsafe_function(napi_env env, napi_threadsafe_function func) { +napi_status NAPI_CDECL napi_ref_threadsafe_function( + node_api_nogc_env env, napi_threadsafe_function func) { CHECK_NOT_NULL(func); return reinterpret_cast(func)->Ref(); } -napi_status NAPI_CDECL node_api_get_module_file_name(napi_env env, +napi_status NAPI_CDECL node_api_get_module_file_name(node_api_nogc_env nogc_env, const char** result) { + napi_env env = const_cast(nogc_env); CHECK_ENV(env); CHECK_ARG(env, result); diff --git a/src/node_api.h b/src/node_api.h index 49a23aed9c6e90..0074124dc6aa4f 100644 --- a/src/node_api.h +++ b/src/node_api.h @@ -131,7 +131,7 @@ NAPI_EXTERN napi_status NAPI_CDECL napi_create_external_buffer(napi_env env, size_t length, void* data, - napi_finalize finalize_cb, + node_api_nogc_finalize finalize_cb, void* finalize_hint, napi_value* result); #endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED @@ -159,20 +159,20 @@ napi_create_async_work(napi_env env, napi_async_work* result); NAPI_EXTERN napi_status NAPI_CDECL napi_delete_async_work(napi_env env, napi_async_work work); -NAPI_EXTERN napi_status NAPI_CDECL napi_queue_async_work(napi_env env, +NAPI_EXTERN napi_status NAPI_CDECL napi_queue_async_work(node_api_nogc_env env, napi_async_work work); -NAPI_EXTERN napi_status NAPI_CDECL napi_cancel_async_work(napi_env env, +NAPI_EXTERN napi_status NAPI_CDECL napi_cancel_async_work(node_api_nogc_env env, napi_async_work work); // version management NAPI_EXTERN napi_status NAPI_CDECL -napi_get_node_version(napi_env env, const napi_node_version** version); +napi_get_node_version(node_api_nogc_env env, const napi_node_version** version); #if NAPI_VERSION >= 2 // Return the current libuv event loop for a given environment NAPI_EXTERN napi_status NAPI_CDECL -napi_get_uv_event_loop(napi_env env, struct uv_loop_s** loop); +napi_get_uv_event_loop(node_api_nogc_env env, struct uv_loop_s** loop); #endif // NAPI_VERSION >= 2 @@ -181,11 +181,11 @@ napi_get_uv_event_loop(napi_env env, struct uv_loop_s** loop); NAPI_EXTERN napi_status NAPI_CDECL napi_fatal_exception(napi_env env, napi_value err); -NAPI_EXTERN napi_status NAPI_CDECL -napi_add_env_cleanup_hook(napi_env env, napi_cleanup_hook fun, void* arg); +NAPI_EXTERN napi_status NAPI_CDECL napi_add_env_cleanup_hook( + node_api_nogc_env env, napi_cleanup_hook fun, void* arg); -NAPI_EXTERN napi_status NAPI_CDECL -napi_remove_env_cleanup_hook(napi_env env, napi_cleanup_hook fun, void* arg); +NAPI_EXTERN napi_status NAPI_CDECL napi_remove_env_cleanup_hook( + node_api_nogc_env env, napi_cleanup_hook fun, void* arg); NAPI_EXTERN napi_status NAPI_CDECL napi_open_callback_scope(napi_env env, @@ -209,7 +209,7 @@ napi_create_threadsafe_function(napi_env env, size_t max_queue_size, size_t initial_thread_count, void* thread_finalize_data, - napi_finalize thread_finalize_cb, + node_api_nogc_finalize thread_finalize_cb, void* context, napi_threadsafe_function_call_js call_js_cb, napi_threadsafe_function* result); @@ -228,18 +228,18 @@ napi_acquire_threadsafe_function(napi_threadsafe_function func); NAPI_EXTERN napi_status NAPI_CDECL napi_release_threadsafe_function( napi_threadsafe_function func, napi_threadsafe_function_release_mode mode); -NAPI_EXTERN napi_status NAPI_CDECL -napi_unref_threadsafe_function(napi_env env, napi_threadsafe_function func); +NAPI_EXTERN napi_status NAPI_CDECL napi_unref_threadsafe_function( + node_api_nogc_env env, napi_threadsafe_function func); -NAPI_EXTERN napi_status NAPI_CDECL -napi_ref_threadsafe_function(napi_env env, napi_threadsafe_function func); +NAPI_EXTERN napi_status NAPI_CDECL napi_ref_threadsafe_function( + node_api_nogc_env env, napi_threadsafe_function func); #endif // NAPI_VERSION >= 4 #if NAPI_VERSION >= 8 NAPI_EXTERN napi_status NAPI_CDECL -napi_add_async_cleanup_hook(napi_env env, +napi_add_async_cleanup_hook(node_api_nogc_env env, napi_async_cleanup_hook hook, void* arg, napi_async_cleanup_hook_handle* remove_handle); @@ -252,7 +252,7 @@ napi_remove_async_cleanup_hook(napi_async_cleanup_hook_handle remove_handle); #if NAPI_VERSION >= 9 NAPI_EXTERN napi_status NAPI_CDECL -node_api_get_module_file_name(napi_env env, const char** result); +node_api_get_module_file_name(node_api_nogc_env env, const char** result); #endif // NAPI_VERSION >= 9 diff --git a/test/js-native-api/common.h b/test/js-native-api/common.h index fc6f4cb6c2e1db..7822e55b2bd44a 100644 --- a/test/js-native-api/common.h +++ b/test/js-native-api/common.h @@ -2,24 +2,38 @@ #define JS_NATIVE_API_COMMON_H_ #include +#include +#include // Empty value so that macros here are able to return NULL or void #define NODE_API_RETVAL_NOTHING // Intentionally blank #define -#define GET_AND_THROW_LAST_ERROR(env) \ - do { \ - const napi_extended_error_info *error_info; \ - napi_get_last_error_info((env), &error_info); \ - bool is_pending; \ - const char* err_message = error_info->error_message; \ - napi_is_exception_pending((env), &is_pending); \ - /* If an exception is already pending, don't rethrow it */ \ - if (!is_pending) { \ - const char* error_message = err_message != NULL ? \ - err_message : \ - "empty error message"; \ - napi_throw_error((env), NULL, error_message); \ - } \ +#define GET_AND_THROW_LAST_ERROR(env) \ + do { \ + const napi_extended_error_info* error_info; \ + napi_get_last_error_info((env), &error_info); \ + bool is_pending; \ + const char* err_message = error_info->error_message; \ + napi_is_exception_pending((env), &is_pending); \ + /* If an exception is already pending, don't rethrow it */ \ + if (!is_pending) { \ + const char* error_message = \ + err_message != NULL ? err_message : "empty error message"; \ + napi_throw_error((env), NULL, error_message); \ + } \ + } while (0) + +// The pure version of GET_AND_THROW_LAST_ERROR. We cannot access any +// exceptions and we cannot fail by way of JS exception, so we abort. +#define FATALLY_FAIL_WITH_LAST_ERROR(env) \ + do { \ + const napi_extended_error_info* error_info; \ + napi_get_last_error_info((env), &error_info); \ + const char* err_message = error_info->error_message; \ + const char* error_message = \ + err_message != NULL ? err_message : "empty error message"; \ + fprintf(stderr, "%s\n", error_message); \ + abort(); \ } while (0) #define NODE_API_ASSERT_BASE(env, assertion, message, ret_val) \ @@ -33,6 +47,15 @@ } \ } while (0) +#define NODE_API_NOGC_ASSERT_BASE(assertion, message, ret_val) \ + do { \ + if (!(assertion)) { \ + fprintf(stderr, "assertion (" #assertion ") failed: " message); \ + abort(); \ + return ret_val; \ + } \ + } while (0) + // Returns NULL on failed assertion. // This is meant to be used inside napi_callback methods. #define NODE_API_ASSERT(env, assertion, message) \ @@ -43,6 +66,9 @@ #define NODE_API_ASSERT_RETURN_VOID(env, assertion, message) \ NODE_API_ASSERT_BASE(env, assertion, message, NODE_API_RETVAL_NOTHING) +#define NODE_API_NOGC_ASSERT_RETURN_VOID(assertion, message) \ + NODE_API_NOGC_ASSERT_BASE(assertion, message, NODE_API_RETVAL_NOTHING) + #define NODE_API_CALL_BASE(env, the_call, ret_val) \ do { \ if ((the_call) != napi_ok) { \ @@ -51,6 +77,14 @@ } \ } while (0) +#define NODE_API_NOGC_CALL_BASE(env, the_call, ret_val) \ + do { \ + if ((the_call) != napi_ok) { \ + FATALLY_FAIL_WITH_LAST_ERROR((env)); \ + return ret_val; \ + } \ + } while (0) + // Returns NULL if the_call doesn't return napi_ok. #define NODE_API_CALL(env, the_call) \ NODE_API_CALL_BASE(env, the_call, NULL) @@ -59,6 +93,9 @@ #define NODE_API_CALL_RETURN_VOID(env, the_call) \ NODE_API_CALL_BASE(env, the_call, NODE_API_RETVAL_NOTHING) +#define NODE_API_NOGC_CALL_RETURN_VOID(env, the_call) \ + NODE_API_NOGC_CALL_BASE(env, the_call, NODE_API_RETVAL_NOTHING) + #define NODE_API_CHECK_STATUS(the_call) \ do { \ napi_status status = (the_call); \ diff --git a/test/js-native-api/test_cannot_run_js/binding.gyp b/test/js-native-api/test_cannot_run_js/binding.gyp index 0b827ff34d129f..408181240097da 100644 --- a/test/js-native-api/test_cannot_run_js/binding.gyp +++ b/test/js-native-api/test_cannot_run_js/binding.gyp @@ -5,7 +5,7 @@ "sources": [ "test_cannot_run_js.c" ], - "defines": [ "NAPI_EXPERIMENTAL" ], + "defines": [ "NAPI_EXPERIMENTAL", "NODE_API_EXPERIMENTAL_NOGC_ENV" ], }, { "target_name": "test_pending_exception", diff --git a/test/js-native-api/test_cannot_run_js/test_cannot_run_js.c b/test/js-native-api/test_cannot_run_js/test_cannot_run_js.c index 2cd25823c924c0..be410389a054c9 100644 --- a/test/js-native-api/test_cannot_run_js/test_cannot_run_js.c +++ b/test/js-native-api/test_cannot_run_js/test_cannot_run_js.c @@ -20,6 +20,15 @@ static void Finalize(napi_env env, void* data, void* hint) { free(ref); } +static void PureFinalize(node_api_nogc_env env, void* data, void* hint) { +#if defined(NAPI_EXPERIMENTAL) && defined(NODE_API_EXPERIMENTAL_NOGC_ENV) + NODE_API_NOGC_CALL_RETURN_VOID( + env, node_api_post_finalizer(env, Finalize, data, hint)); +#else + Finalize(env, data, hint); +#endif // NAPI_EXPERIMENTAL && NODE_API_EXPERIMENTAL_NOGC_ENV +} + static napi_value CreateRef(napi_env env, napi_callback_info info) { size_t argc = 1; napi_value cb; @@ -30,7 +39,7 @@ static napi_value CreateRef(napi_env env, napi_callback_info info) { NODE_API_CALL(env, napi_typeof(env, cb, &value_type)); NODE_API_ASSERT( env, value_type == napi_function, "argument must be function"); - NODE_API_CALL(env, napi_add_finalizer(env, cb, ref, Finalize, NULL, ref)); + NODE_API_CALL(env, napi_add_finalizer(env, cb, ref, PureFinalize, NULL, ref)); return cb; } diff --git a/test/js-native-api/test_finalizer/binding.gyp b/test/js-native-api/test_finalizer/binding.gyp index 4c63346f30ce74..6c9c8020e3ea95 100644 --- a/test/js-native-api/test_finalizer/binding.gyp +++ b/test/js-native-api/test_finalizer/binding.gyp @@ -2,7 +2,7 @@ "targets": [ { "target_name": "test_finalizer", - "defines": [ "NAPI_EXPERIMENTAL" ], + "defines": [ "NAPI_EXPERIMENTAL", "NODE_API_EXPERIMENTAL_NOGC_ENV" ], "sources": [ "test_finalizer.c" ] diff --git a/test/js-native-api/test_finalizer/test_finalizer.c b/test/js-native-api/test_finalizer/test_finalizer.c index 378781b7042f96..70ae364e932cea 100644 --- a/test/js-native-api/test_finalizer/test_finalizer.c +++ b/test/js-native-api/test_finalizer/test_finalizer.c @@ -11,17 +11,17 @@ typedef struct { napi_ref js_func; } FinalizerData; -static void finalizerOnlyCallback(napi_env env, +static void finalizerOnlyCallback(node_api_nogc_env env, void* finalize_data, void* finalize_hint) { FinalizerData* data = (FinalizerData*)finalize_data; int32_t count = ++data->finalize_count; // It is safe to access instance data - NODE_API_CALL_RETURN_VOID(env, napi_get_instance_data(env, (void**)&data)); - NODE_API_ASSERT_RETURN_VOID(env, - count = data->finalize_count, - "Expected to be the same FinalizerData"); + NODE_API_NOGC_CALL_RETURN_VOID(env, + napi_get_instance_data(env, (void**)&data)); + NODE_API_NOGC_ASSERT_RETURN_VOID(count = data->finalize_count, + "Expected to be the same FinalizerData"); } static void finalizerCallingJSCallback(napi_env env, @@ -40,22 +40,24 @@ static void finalizerCallingJSCallback(napi_env env, } // Schedule async finalizer to run JavaScript-touching code. -static void finalizerWithJSCallback(napi_env env, +static void finalizerWithJSCallback(node_api_nogc_env env, void* finalize_data, void* finalize_hint) { - NODE_API_CALL_RETURN_VOID( + NODE_API_NOGC_CALL_RETURN_VOID( env, node_api_post_finalizer( env, finalizerCallingJSCallback, finalize_data, finalize_hint)); } -static void finalizerWithFailedJSCallback(napi_env env, +static void finalizerWithFailedJSCallback(node_api_nogc_env env, void* finalize_data, void* finalize_hint) { napi_value obj; FinalizerData* data = (FinalizerData*)finalize_data; ++data->finalize_count; - NODE_API_CALL_RETURN_VOID(env, napi_create_object(env, &obj)); + // We intentionally cast to napi_env and call an API that cannot be called + // during garbage collection. This will cause the process to fail. + NODE_API_NOGC_CALL_RETURN_VOID(env, napi_create_object((napi_env)env, &obj)); } static napi_value addFinalizer(napi_env env, napi_callback_info info) { diff --git a/test/js-native-api/test_string/binding.gyp b/test/js-native-api/test_string/binding.gyp index 63fec1ae3b5375..7d29e61a2a9bda 100644 --- a/test/js-native-api/test_string/binding.gyp +++ b/test/js-native-api/test_string/binding.gyp @@ -5,6 +5,9 @@ "sources": [ "test_string.c", "test_null.c", + ], + "defines": [ + "NAPI_EXPERIMENTAL", "NODE_API_EXPERIMENTAL_NOGC_ENV" ] } ] diff --git a/test/js-native-api/test_string/test_string.c b/test/js-native-api/test_string/test_string.c index b2046e3b873392..81da3d61fe18ac 100644 --- a/test/js-native-api/test_string/test_string.c +++ b/test/js-native-api/test_string/test_string.c @@ -1,8 +1,7 @@ +#include #include // INT_MAX #include #include -#define NAPI_EXPERIMENTAL -#include #include "../common.h" #include "../entry_point.h" #include "test_null.h" @@ -88,7 +87,7 @@ static napi_value TestTwoByteImpl(napi_env env, return output; } -static void free_string(napi_env env, void* data, void* hint) { +static void free_string(node_api_nogc_env env, void* data, void* hint) { free(data); } diff --git a/test/node-api/test_async/test_async.c b/test/node-api/test_async/test_async.c index 21402721895de4..6a253e8234ef8e 100644 --- a/test/node-api/test_async/test_async.c +++ b/test/node-api/test_async/test_async.c @@ -200,7 +200,7 @@ static napi_value DoRepeatedWork(napi_env env, napi_callback_info info) { return NULL; } -static napi_value Init(napi_env env, napi_value exports) { +NAPI_MODULE_INIT(/* napi_env env, napi_value exports */) { napi_property_descriptor properties[] = { DECLARE_NODE_API_PROPERTY("Test", Test), DECLARE_NODE_API_PROPERTY("TestCancel", TestCancel), @@ -212,5 +212,3 @@ static napi_value Init(napi_env env, napi_value exports) { return exports; } - -NAPI_MODULE(NODE_GYP_MODULE_NAME, Init) diff --git a/test/node-api/test_callback_scope/binding.c b/test/node-api/test_callback_scope/binding.c index e028d517a52e37..a3bbf8258397f8 100644 --- a/test/node-api/test_callback_scope/binding.c +++ b/test/node-api/test_callback_scope/binding.c @@ -104,7 +104,7 @@ static napi_value TestResolveAsync(napi_env env, napi_callback_info info) { return promise; } -static napi_value Init(napi_env env, napi_value exports) { +NAPI_MODULE_INIT(/* napi_env env, napi_value exports */) { napi_property_descriptor descriptors[] = { DECLARE_NODE_API_PROPERTY("runInCallbackScope", RunInCallbackScope), DECLARE_NODE_API_PROPERTY("testResolveAsync", TestResolveAsync) @@ -115,5 +115,3 @@ static napi_value Init(napi_env env, napi_value exports) { return exports; } - -NAPI_MODULE(NODE_GYP_MODULE_NAME, Init) diff --git a/test/node-api/test_env_teardown_gc/binding.c b/test/node-api/test_env_teardown_gc/binding.c index e3ba1cee3edc37..08fb6de605d0d5 100644 --- a/test/node-api/test_env_teardown_gc/binding.c +++ b/test/node-api/test_env_teardown_gc/binding.c @@ -2,7 +2,7 @@ #include #include "../../js-native-api/common.h" -static void MyObject_fini(napi_env env, void* data, void* hint) { +static void MyObject_fini(node_api_nogc_env env, void* data, void* hint) { napi_ref* ref = data; napi_value global; napi_value cleanup; diff --git a/test/node-api/test_general/test_general.c b/test/node-api/test_general/test_general.c index ece1f2703b4aec..2a376e839686b3 100644 --- a/test/node-api/test_general/test_general.c +++ b/test/node-api/test_general/test_general.c @@ -37,7 +37,7 @@ static napi_value GetFilename(napi_env env, napi_callback_info info) { return result; } -static napi_value Init(napi_env env, napi_value exports) { +NAPI_MODULE_INIT(/*napi_env env, napi_value exports*/) { napi_property_descriptor descriptors[] = { DECLARE_NODE_API_PROPERTY("testGetNodeVersion", testGetNodeVersion), DECLARE_NODE_API_GETTER("filename", GetFilename), @@ -48,5 +48,3 @@ static napi_value Init(napi_env env, napi_value exports) { return exports; } - -NAPI_MODULE(NODE_GYP_MODULE_NAME, Init) diff --git a/test/node-api/test_reference_by_node_api_version/binding.gyp b/test/node-api/test_reference_by_node_api_version/binding.gyp index 2ee1d24763b0b3..d246e970c74486 100644 --- a/test/node-api/test_reference_by_node_api_version/binding.gyp +++ b/test/node-api/test_reference_by_node_api_version/binding.gyp @@ -3,7 +3,7 @@ { "target_name": "test_reference_all_types", "sources": [ "test_reference_by_node_api_version.c" ], - "defines": [ "NAPI_EXPERIMENTAL" ], + "defines": [ "NAPI_EXPERIMENTAL", "NODE_API_EXPERIMENTAL_NOGC_ENV" ], }, { "target_name": "test_reference_obj_only", diff --git a/test/node-api/test_reference_by_node_api_version/test_reference_by_node_api_version.c b/test/node-api/test_reference_by_node_api_version/test_reference_by_node_api_version.c index ae4fa7cb0e8856..f9110303d2ded4 100644 --- a/test/node-api/test_reference_by_node_api_version/test_reference_by_node_api_version.c +++ b/test/node-api/test_reference_by_node_api_version/test_reference_by_node_api_version.c @@ -4,12 +4,12 @@ static uint32_t finalizeCount = 0; -static void FreeData(napi_env env, void* data, void* hint) { - NODE_API_ASSERT_RETURN_VOID(env, data != NULL, "Expects non-NULL data."); +static void FreeData(node_api_nogc_env env, void* data, void* hint) { + NODE_API_NOGC_ASSERT_RETURN_VOID(data != NULL, "Expects non-NULL data."); free(data); } -static void Finalize(napi_env env, void* data, void* hint) { +static void Finalize(node_api_nogc_env env, void* data, void* hint) { ++finalizeCount; } @@ -61,7 +61,7 @@ static napi_value ToUInt32Value(napi_env env, uint32_t value) { static napi_status InitRefArray(napi_env env) { // valueRefs array has one entry per napi_valuetype napi_ref* valueRefs = malloc(sizeof(napi_ref) * ((int)napi_bigint + 1)); - return napi_set_instance_data(env, valueRefs, &FreeData, NULL); + return napi_set_instance_data(env, valueRefs, (napi_finalize)&FreeData, NULL); } static napi_value CreateExternal(napi_env env, napi_callback_info info) { diff --git a/test/node-api/test_threadsafe_function/binding.gyp b/test/node-api/test_threadsafe_function/binding.gyp index 58a9d04d4a5619..a01d962057a8c4 100644 --- a/test/node-api/test_threadsafe_function/binding.gyp +++ b/test/node-api/test_threadsafe_function/binding.gyp @@ -14,7 +14,7 @@ { 'target_name': 'test_uncaught_exception', 'defines': [ - 'NAPI_EXPERIMENTAL' + 'NAPI_EXPERIMENTAL', 'NODE_API_EXPERIMENTAL_NOGC_ENV' ], 'sources': ['test_uncaught_exception.c'] } diff --git a/test/node-api/test_threadsafe_function/test_uncaught_exception.c b/test/node-api/test_threadsafe_function/test_uncaught_exception.c index f8499d4fe4d680..b7068c5f17b9e7 100644 --- a/test/node-api/test_threadsafe_function/test_uncaught_exception.c +++ b/test/node-api/test_threadsafe_function/test_uncaught_exception.c @@ -2,9 +2,9 @@ #include "../../js-native-api/common.h" // Testing calling into JavaScript -static void ThreadSafeFunctionFinalize(napi_env env, - void* finalize_data, - void* finalize_hint) { +static void ThreadSafeFunctionJsCapableFinalize(napi_env env, + void* finalize_data, + void* finalize_hint) { napi_ref js_func_ref = (napi_ref)finalize_data; napi_value js_func; napi_value recv; @@ -16,6 +16,21 @@ static void ThreadSafeFunctionFinalize(napi_env env, NODE_API_CALL_RETURN_VOID(env, napi_delete_reference(env, js_func_ref)); } +static void ThreadSafeFunctionFinalize(node_api_nogc_env env, + void* finalize_data, + void* finalize_hint) { +#if defined(NAPI_EXPERIMENTAL) && defined(NODE_API_EXPERIMENTAL_NOGC_ENV) + NODE_API_NOGC_CALL_RETURN_VOID( + env, + node_api_post_finalizer(env, + ThreadSafeFunctionJsCapableFinalize, + finalize_data, + finalize_hint)); +#else + ThreadSafeFunctionJsCapableFinalize(env, finalize_data, finalize_hint); +#endif // NAPI_EXPERIMENTAL && NODE_API_EXPERIMENTAL_NOGC_ENV +} + // Testing calling into JavaScript static napi_value CallIntoModule(napi_env env, napi_callback_info info) { size_t argc = 4;