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.

PR-URL: https://github.com/nodejs/node/pull/50060
Reviewed-By: Chengzhong Wu <legendecas@gmail.com>
Reviewed-By: Vladimir Morozov <vmorozov@microsoft.com>
Reviewed-By: Michael Dawson <midawson@redhat.com>
This commit is contained in:
Gabriel Schulhof 2023-12-18 23:37:15 -08:00 committed by GitHub
parent 8573146f72
commit 7a216d5fd6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 368 additions and 146 deletions

View File

@ -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][] When using `node-addon-api` instead of the C APIs, start with the API [docs][]
for `node-addon-api`. 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 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 and `node-addon-api`. Additional media resources can be found on the
[Node-API Media][] page. [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 an alternative build system based on [CMake][].
CMake.js is a good choice for projects that already use CMake or for 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 ### 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 In this case the entire API surface, including any experimental APIs, will be
available to the module code. available to the module code.
Occasionally, experimental features are introduced that affect already-released
and stable APIs. These features can be disabled by an opt-out:
```c
#define NAPI_EXPERIMENTAL
#define NODE_API_EXPERIMENTAL_<FEATURE_NAME>_OPT_OUT
#include <node_api.h>
```
where `<FEATURE_NAME>` is the name of an experimental feature that affects both
experimental and stable APIs.
## Node-API version matrix ## Node-API version matrix
Up until version 9, Node-API versions were additive and versioned Up until version 9, Node-API versions were additive and versioned
@ -419,7 +432,7 @@ napi_value create_addon(napi_env env) {
#include <node_api.h> #include <node_api.h>
#include "addon.h" #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`. // This function body is expected to return a `napi_value`.
// The variables `napi_env env` and `napi_value exports` may be used within // 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()`. // the body, as they are provided by the definition of `NAPI_MODULE_INIT()`.
@ -464,7 +477,7 @@ napiVersion: 6
--> -->
```c ```c
napi_status napi_set_instance_data(napi_env env, napi_status napi_set_instance_data(node_api_nogc_env env,
void* data, void* data,
napi_finalize finalize_cb, napi_finalize finalize_cb,
void* finalize_hint); void* finalize_hint);
@ -496,7 +509,7 @@ napiVersion: 6
--> -->
```c ```c
napi_status napi_get_instance_data(napi_env env, napi_status napi_get_instance_data(node_api_nogc_env env,
void** data); 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 delivered through the callbacks given to [`napi_add_env_cleanup_hook`][] and
[`napi_set_instance_data`][]. [`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` ### `napi_value`
This is an opaque pointer that is used to represent a JavaScript 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 Unless for reasons discussed in [Object Lifetime Management][], creating a
handle and/or callback scope inside a `napi_callback` is not necessary. handle and/or callback scope inside a `napi_callback` is not necessary.
#### `napi_finalize` #### `node_api_nogc_finalize`
<!-- YAML <!-- YAML
added: v8.0.0 added: REPLACEME
napiVersion: 1
--> -->
> Stability: 1 - Experimental
Function pointer type for add-on provided functions that allow the user to be 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 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 object it was associated with has been garbage-collected. The user must provide
must provide a function satisfying the following signature which would get a function satisfying the following signature which would get called upon the
called upon the object's collection. Currently, `napi_finalize` can be used for object's collection. Currently, `node_api_nogc_finalize` can be used for
finding out when objects that have external data are collected. finding out when objects that have external data are collected.
```c ```c
typedef void (*napi_finalize)(napi_env env, typedef void (*node_api_nogc_finalize)(node_api_nogc_env env,
void* finalize_data, void* finalize_data,
void* finalize_hint); void* finalize_hint);
``` ```
Unless for reasons discussed in [Object Lifetime Management][], creating a Unless for reasons discussed in [Object Lifetime Management][], creating a
handle and/or callback scope inside the function body is not necessary. 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 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 where it cannot execute JavaScript code, only Node-APIs which accept a
`napi_pending_exception` even when there is no exception pending. `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 In the case of [`node_api_create_external_string_latin1`][] and
[`node_api_create_external_string_utf16`][] the `env` parameter may be null, [`node_api_create_external_string_utf16`][] the `env` parameter may be null,
@ -796,11 +829,39 @@ shutdown.
Change History: Change History:
* experimental (`NAPI_EXPERIMENTAL`):
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. This feature can be turned off by defining
`NODE_API_EXPERIMENTAL_NOGC_ENV_OPT_OUT`.
#### `napi_finalize`
<!-- YAML
added: v8.0.0
napiVersion: 1
-->
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`][].
```c
typedef void (*napi_finalize)(napi_env env,
void* finalize_data,
void* finalize_hint);
```
Change History:
* experimental (`NAPI_EXPERIMENTAL` is defined): * experimental (`NAPI_EXPERIMENTAL` is defined):
Node-API calls made from a finalizer will return `napi_cannot_run_js` when A function of this type may no longer be used as a finalizer, except with
the JavaScript engine is unable to execute JavaScript, and will return [`node_api_post_finalizer`][]. [`node_api_nogc_finalize`][] must be used
`napi_exception_pending` if there is a pending exception. instead. This feature can be turned off by defining
`NODE_API_EXPERIMENTAL_NOGC_ENV_OPT_OUT`.
#### `napi_async_execute_callback` #### `napi_async_execute_callback`
@ -1002,7 +1063,7 @@ napiVersion: 1
```c ```c
napi_status 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); const napi_extended_error_info** result);
``` ```
@ -1821,7 +1882,7 @@ napiVersion: 3
--> -->
```c ```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, napi_cleanup_hook fun,
void* arg); void* arg);
``` ```
@ -1851,7 +1912,7 @@ napiVersion: 3
--> -->
```c ```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 (*fun)(void* arg),
void* arg); void* arg);
``` ```
@ -1880,7 +1941,7 @@ changes:
```c ```c
NAPI_EXTERN napi_status napi_add_async_cleanup_hook( NAPI_EXTERN napi_status napi_add_async_cleanup_hook(
napi_env env, node_api_nogc_env env,
napi_async_cleanup_hook hook, napi_async_cleanup_hook hook,
void* arg, void* arg,
napi_async_cleanup_hook_handle* remove_handle); napi_async_cleanup_hook_handle* remove_handle);
@ -2038,7 +2099,7 @@ You can also use the `NAPI_MODULE_INIT` macro, which acts as a shorthand
for `NAPI_MODULE` and defining an `Init` function: for `NAPI_MODULE` and defining an `Init` function:
```c ```c
NAPI_MODULE_INIT() { NAPI_MODULE_INIT(/* napi_env env, napi_value exports */) {
napi_value answer; napi_value answer;
napi_status result; napi_status result;
@ -2052,6 +2113,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 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. times. There are a few design considerations when declaring such a module.
The documentation on [context-aware addons][] provides more details. The documentation on [context-aware addons][] provides more details.
@ -5420,7 +5484,7 @@ napiVersion: 5
napi_status napi_add_finalizer(napi_env env, napi_status napi_add_finalizer(napi_env env,
napi_value js_object, napi_value js_object,
void* finalize_data, void* finalize_data,
napi_finalize finalize_cb, node_api_nogc_finalize finalize_cb,
void* finalize_hint, void* finalize_hint,
napi_ref* result); napi_ref* result);
``` ```
@ -5461,7 +5525,7 @@ added:
> Stability: 1 - Experimental > Stability: 1 - Experimental
```c ```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, napi_finalize finalize_cb,
void* finalize_data, void* finalize_data,
void* finalize_hint); void* finalize_hint);
@ -5531,7 +5595,7 @@ Once created the async worker can be queued
for execution using the [`napi_queue_async_work`][] function: for execution using the [`napi_queue_async_work`][] function:
```c ```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); napi_async_work work);
``` ```
@ -5623,7 +5687,7 @@ napiVersion: 1
--> -->
```c ```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); napi_async_work work);
``` ```
@ -5644,7 +5708,7 @@ napiVersion: 1
--> -->
```c ```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); napi_async_work work);
``` ```
@ -5848,7 +5912,7 @@ typedef struct {
const char* release; const char* release;
} napi_node_version; } 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); const napi_node_version** version);
``` ```
@ -5871,7 +5935,7 @@ napiVersion: 1
--> -->
```c ```c
napi_status napi_get_version(napi_env env, napi_status napi_get_version(node_api_nogc_env env,
uint32_t* result); uint32_t* result);
``` ```
@ -5904,7 +5968,7 @@ napiVersion: 1
--> -->
```c ```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 change_in_bytes,
int64_t* result); int64_t* result);
``` ```
@ -6121,7 +6185,7 @@ napiVersion: 2
--> -->
```c ```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); struct uv_loop_s** loop);
``` ```
@ -6435,7 +6499,7 @@ napiVersion: 4
```c ```c
NAPI_EXTERN napi_status 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. * `[in] env`: The environment that the API is invoked under.
@ -6461,7 +6525,7 @@ napiVersion: 4
```c ```c
NAPI_EXTERN napi_status 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. * `[in] env`: The environment that the API is invoked under.
@ -6487,7 +6551,7 @@ napiVersion: 9
```c ```c
NAPI_EXTERN napi_status 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);
``` ```
@ -6551,6 +6615,7 @@ the add-on's file name during loading.
[`Number.MIN_SAFE_INTEGER`]: https://tc39.github.io/ecma262/#sec-number.min_safe_integer [`Number.MIN_SAFE_INTEGER`]: https://tc39.github.io/ecma262/#sec-number.min_safe_integer
[`Worker`]: worker_threads.md#class-worker [`Worker`]: worker_threads.md#class-worker
[`async_hooks.executionAsyncResource()`]: async_hooks.md#async_hooksexecutionasyncresource [`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 [`global`]: globals.md#global
[`init` hooks]: async_hooks.md#initasyncid-type-triggerasyncid-resource [`init` hooks]: async_hooks.md#initasyncid-type-triggerasyncid-resource
[`napi_add_async_cleanup_hook`]: #napi_add_async_cleanup_hook [`napi_add_async_cleanup_hook`]: #napi_add_async_cleanup_hook
@ -6614,6 +6679,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_latin1`]: #node_api_create_external_string_latin1
[`node_api_create_external_string_utf16`]: #node_api_create_external_string_utf16 [`node_api_create_external_string_utf16`]: #node_api_create_external_string_utf16
[`node_api_create_syntax_error`]: #node_api_create_syntax_error [`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 [`node_api_throw_syntax_error`]: #node_api_throw_syntax_error
[`process.release`]: process.md#processrelease [`process.release`]: process.md#processrelease
[`uv_ref`]: https://docs.libuv.org/en/v1.x/handle.html#c.uv_ref [`uv_ref`]: https://docs.libuv.org/en/v1.x/handle.html#c.uv_ref

View File

@ -51,3 +51,16 @@ Node-API.
to the decision to take an API out of experimental status. to the decision to take an API out of experimental status.
* The API **must** be implemented in a Node.js implementation with an * The API **must** be implemented in a Node.js implementation with an
alternate VM. 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 accompanied by an opt-out flag. This provides add-on
maintainers who take advantage of the initial compile-time flag to track
impending changes to Node-API with
* a quick fix to the breakage caused,
* a notification that such breakage is impending, and thus
* a buffer to adoption above and beyond the one provided by the initial
compile-time flag.

View File

@ -84,7 +84,11 @@ define guards on the declaration of the new Node-API. Check for these guards
with: with:
```bash ```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: and update the define version guards with the release version:
@ -102,6 +106,9 @@ and update the define version guards with the release version:
Remove any feature flags of the form `NODE_API_EXPERIMENTAL_HAS_<FEATURE>`. Remove any feature flags of the form `NODE_API_EXPERIMENTAL_HAS_<FEATURE>`.
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 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`: `test/js-native-api/test_general/test.js` with the release version `x`:
@ -130,7 +137,11 @@ commits should already include `NAPI_EXPERIMENTAL` definition for the tests.
Check for these definitions with: Check for these definitions with:
```bash ```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 and substitute the `NAPI_EXPERIMENTAL` with the release version
@ -141,6 +152,8 @@ and substitute the `NAPI_EXPERIMENTAL` with the release version
+ #define NAPI_VERSION 10 + #define NAPI_VERSION 10
``` ```
Remove any `NODE_API_EXPERIMENTAL_*` flags.
#### Step 4. Update document #### Step 4. Update document
If this release includes new Node-APIs that were first released in this If this release includes new Node-APIs that were first released in this

View File

@ -49,8 +49,8 @@
EXTERN_C_START EXTERN_C_START
NAPI_EXTERN napi_status NAPI_CDECL NAPI_EXTERN napi_status NAPI_CDECL napi_get_last_error_info(
napi_get_last_error_info(napi_env env, const napi_extended_error_info** result); node_api_nogc_env env, const napi_extended_error_info** result);
// Getters for defined singletons // Getters for defined singletons
NAPI_EXTERN napi_status NAPI_CDECL napi_get_undefined(napi_env env, NAPI_EXTERN napi_status NAPI_CDECL napi_get_undefined(napi_env env,
@ -98,7 +98,7 @@ NAPI_EXTERN napi_status NAPI_CDECL
node_api_create_external_string_latin1(napi_env env, node_api_create_external_string_latin1(napi_env env,
char* str, char* str,
size_t length, size_t length,
napi_finalize finalize_callback, node_api_nogc_finalize finalize_callback,
void* finalize_hint, void* finalize_hint,
napi_value* result, napi_value* result,
bool* copied); bool* copied);
@ -106,7 +106,7 @@ NAPI_EXTERN napi_status NAPI_CDECL
node_api_create_external_string_utf16(napi_env env, node_api_create_external_string_utf16(napi_env env,
char16_t* str, char16_t* str,
size_t length, size_t length,
napi_finalize finalize_callback, node_api_nogc_finalize finalize_callback,
void* finalize_hint, void* finalize_hint,
napi_value* result, napi_value* result,
bool* copied); bool* copied);
@ -290,7 +290,7 @@ NAPI_EXTERN napi_status NAPI_CDECL napi_instanceof(napi_env env,
// Gets all callback info in a single call. (Ugly, but faster.) // Gets all callback info in a single call. (Ugly, but faster.)
NAPI_EXTERN napi_status NAPI_CDECL napi_get_cb_info( 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 napi_callback_info cbinfo, // [in] Opaque callback-info handle
size_t* argc, // [in-out] Specifies the size of the provided argv array size_t* argc, // [in-out] Specifies the size of the provided argv array
// and receives the actual count of args. // and receives the actual count of args.
@ -314,7 +314,7 @@ napi_define_class(napi_env env,
NAPI_EXTERN napi_status NAPI_CDECL napi_wrap(napi_env env, NAPI_EXTERN napi_status NAPI_CDECL napi_wrap(napi_env env,
napi_value js_object, napi_value js_object,
void* native_object, void* native_object,
napi_finalize finalize_cb, node_api_nogc_finalize finalize_cb,
void* finalize_hint, void* finalize_hint,
napi_ref* result); napi_ref* result);
NAPI_EXTERN napi_status NAPI_CDECL napi_unwrap(napi_env env, NAPI_EXTERN napi_status NAPI_CDECL napi_unwrap(napi_env env,
@ -326,7 +326,7 @@ NAPI_EXTERN napi_status NAPI_CDECL napi_remove_wrap(napi_env env,
NAPI_EXTERN napi_status NAPI_CDECL NAPI_EXTERN napi_status NAPI_CDECL
napi_create_external(napi_env env, napi_create_external(napi_env env,
void* data, void* data,
napi_finalize finalize_cb, node_api_nogc_finalize finalize_cb,
void* finalize_hint, void* finalize_hint,
napi_value* result); napi_value* result);
NAPI_EXTERN napi_status NAPI_CDECL napi_get_value_external(napi_env env, NAPI_EXTERN napi_status NAPI_CDECL napi_get_value_external(napi_env env,
@ -425,7 +425,7 @@ NAPI_EXTERN napi_status NAPI_CDECL
napi_create_external_arraybuffer(napi_env env, napi_create_external_arraybuffer(napi_env env,
void* external_data, void* external_data,
size_t byte_length, size_t byte_length,
napi_finalize finalize_cb, node_api_nogc_finalize finalize_cb,
void* finalize_hint, void* finalize_hint,
napi_value* result); napi_value* result);
#endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED #endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED
@ -467,7 +467,7 @@ napi_get_dataview_info(napi_env env,
size_t* byte_offset); size_t* byte_offset);
// version management // 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); uint32_t* result);
// Promises // Promises
@ -491,7 +491,7 @@ NAPI_EXTERN napi_status NAPI_CDECL napi_run_script(napi_env env,
// Memory management // Memory management
NAPI_EXTERN napi_status NAPI_CDECL napi_adjust_external_memory( 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 #if NAPI_VERSION >= 5
@ -509,12 +509,13 @@ NAPI_EXTERN napi_status NAPI_CDECL napi_get_date_value(napi_env env,
double* result); double* result);
// Add finalizer for pointer // Add finalizer for pointer
NAPI_EXTERN napi_status NAPI_CDECL napi_add_finalizer(napi_env env, NAPI_EXTERN napi_status NAPI_CDECL
napi_value js_object, napi_add_finalizer(napi_env env,
void* finalize_data, napi_value js_object,
napi_finalize finalize_cb, void* finalize_data,
void* finalize_hint, node_api_nogc_finalize finalize_cb,
napi_ref* result); void* finalize_hint,
napi_ref* result);
#endif // NAPI_VERSION >= 5 #endif // NAPI_VERSION >= 5
@ -522,7 +523,7 @@ NAPI_EXTERN napi_status NAPI_CDECL napi_add_finalizer(napi_env env,
#define NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER #define NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER
NAPI_EXTERN napi_status NAPI_CDECL 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, napi_finalize finalize_cb,
void* finalize_data, void* finalize_data,
void* finalize_hint); void* finalize_hint);
@ -566,10 +567,13 @@ napi_get_all_property_names(napi_env env,
napi_value* result); napi_value* result);
// Instance data // Instance data
NAPI_EXTERN napi_status NAPI_CDECL napi_set_instance_data( NAPI_EXTERN napi_status NAPI_CDECL
napi_env env, void* data, napi_finalize finalize_cb, void* finalize_hint); 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); void** data);
#endif // NAPI_VERSION >= 6 #endif // NAPI_VERSION >= 6

View File

@ -22,6 +22,35 @@ typedef uint16_t char16_t;
// JSVM API types are all opaque pointers for ABI stability // JSVM API types are all opaque pointers for ABI stability
// typedef undefined structs instead of void* for compile time type safety // typedef undefined structs instead of void* for compile time type safety
typedef struct napi_env__* napi_env; 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(NAPI_EXPERIMENTAL) && \
defined(NODE_API_EXPERIMENTAL_NOGC_ENV_OPT_OUT))
typedef struct napi_env__* node_api_nogc_env;
#else
typedef const struct napi_env__* node_api_nogc_env;
#endif
typedef struct napi_value__* napi_value; typedef struct napi_value__* napi_value;
typedef struct napi_ref__* napi_ref; typedef struct napi_ref__* napi_ref;
typedef struct napi_handle_scope__* napi_handle_scope; typedef struct napi_handle_scope__* napi_handle_scope;
@ -116,6 +145,16 @@ typedef void(NAPI_CDECL* napi_finalize)(napi_env env,
void* finalize_data, void* finalize_data,
void* finalize_hint); void* finalize_hint);
#if !defined(NAPI_EXPERIMENTAL) || \
(defined(NAPI_EXPERIMENTAL) && \
defined(NODE_API_EXPERIMENTAL_NOGC_ENV_OPT_OUT))
typedef napi_finalize node_api_nogc_finalize;
#else
typedef void(NAPI_CDECL* node_api_nogc_finalize)(node_api_nogc_env env,
void* finalize_data,
void* finalize_hint);
#endif
typedef struct { typedef struct {
// One of utf8name or name should be NULL. // One of utf8name or name should be NULL.
const char* utf8name; const char* utf8name;

View File

@ -856,7 +856,8 @@ static const char* error_messages[] = {
}; };
napi_status NAPI_CDECL napi_get_last_error_info( 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<napi_env>(nogc_env);
CHECK_ENV(env); CHECK_ENV(env);
CHECK_ARG(env, result); CHECK_ARG(env, result);
@ -1594,14 +1595,16 @@ napi_status NAPI_CDECL napi_create_string_utf16(napi_env env,
}); });
} }
napi_status NAPI_CDECL napi_status NAPI_CDECL node_api_create_external_string_latin1(
node_api_create_external_string_latin1(napi_env env, napi_env env,
char* str, char* str,
size_t length, size_t length,
napi_finalize finalize_callback, node_api_nogc_finalize nogc_finalize_callback,
void* finalize_hint, void* finalize_hint,
napi_value* result, napi_value* result,
bool* copied) { bool* copied) {
napi_finalize finalize_callback =
reinterpret_cast<napi_finalize>(nogc_finalize_callback);
return v8impl::NewExternalString( return v8impl::NewExternalString(
env, env,
str, str,
@ -1621,14 +1624,16 @@ node_api_create_external_string_latin1(napi_env env,
}); });
} }
napi_status NAPI_CDECL napi_status NAPI_CDECL node_api_create_external_string_utf16(
node_api_create_external_string_utf16(napi_env env, napi_env env,
char16_t* str, char16_t* str,
size_t length, size_t length,
napi_finalize finalize_callback, node_api_nogc_finalize nogc_finalize_callback,
void* finalize_hint, void* finalize_hint,
napi_value* result, napi_value* result,
bool* copied) { bool* copied) {
napi_finalize finalize_callback =
reinterpret_cast<napi_finalize>(nogc_finalize_callback);
return v8impl::NewExternalString( return v8impl::NewExternalString(
env, env,
str, str,
@ -2490,9 +2495,10 @@ GEN_COERCE_FUNCTION(STRING, String, string)
napi_status NAPI_CDECL napi_wrap(napi_env env, napi_status NAPI_CDECL napi_wrap(napi_env env,
napi_value js_object, napi_value js_object,
void* native_object, void* native_object,
napi_finalize finalize_cb, node_api_nogc_finalize nogc_finalize_cb,
void* finalize_hint, void* finalize_hint,
napi_ref* result) { napi_ref* result) {
napi_finalize finalize_cb = reinterpret_cast<napi_finalize>(nogc_finalize_cb);
return v8impl::Wrap( return v8impl::Wrap(
env, js_object, native_object, finalize_cb, finalize_hint, result); env, js_object, native_object, finalize_cb, finalize_hint, result);
} }
@ -2509,11 +2515,13 @@ napi_status NAPI_CDECL napi_remove_wrap(napi_env env,
return v8impl::Unwrap(env, obj, result, v8impl::RemoveWrap); return v8impl::Unwrap(env, obj, result, v8impl::RemoveWrap);
} }
napi_status NAPI_CDECL napi_create_external(napi_env env, napi_status NAPI_CDECL
void* data, napi_create_external(napi_env env,
napi_finalize finalize_cb, void* data,
void* finalize_hint, node_api_nogc_finalize nogc_finalize_cb,
napi_value* result) { void* finalize_hint,
napi_value* result) {
napi_finalize finalize_cb = reinterpret_cast<napi_finalize>(nogc_finalize_cb);
NAPI_PREAMBLE(env); NAPI_PREAMBLE(env);
CHECK_ARG(env, result); CHECK_ARG(env, result);
@ -2932,7 +2940,7 @@ napi_status NAPI_CDECL
napi_create_external_arraybuffer(napi_env env, napi_create_external_arraybuffer(napi_env env,
void* external_data, void* external_data,
size_t byte_length, size_t byte_length,
napi_finalize finalize_cb, node_api_nogc_finalize finalize_cb,
void* finalize_hint, void* finalize_hint,
napi_value* result) { napi_value* result) {
// The API contract here is that the cleanup function runs on the JS thread, // The API contract here is that the cleanup function runs on the JS thread,
@ -3197,7 +3205,8 @@ napi_status NAPI_CDECL napi_get_dataview_info(napi_env env,
return napi_clear_last_error(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_ENV(env);
CHECK_ARG(env, result); CHECK_ARG(env, result);
*result = NODE_API_SUPPORTED_VERSION_MAX; *result = NODE_API_SUPPORTED_VERSION_MAX;
@ -3314,14 +3323,16 @@ napi_status NAPI_CDECL napi_run_script(napi_env env,
return GET_RETURN_STATUS(env); return GET_RETURN_STATUS(env);
} }
napi_status NAPI_CDECL napi_add_finalizer(napi_env env, napi_status NAPI_CDECL
napi_value js_object, napi_add_finalizer(napi_env env,
void* finalize_data, napi_value js_object,
napi_finalize finalize_cb, void* finalize_data,
void* finalize_hint, node_api_nogc_finalize nogc_finalize_cb,
napi_ref* result) { void* finalize_hint,
napi_ref* result) {
// Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw
// JS exceptions. // JS exceptions.
napi_finalize finalize_cb = reinterpret_cast<napi_finalize>(nogc_finalize_cb);
CHECK_ENV_NOT_IN_GC(env); CHECK_ENV_NOT_IN_GC(env);
CHECK_ARG(env, js_object); CHECK_ARG(env, js_object);
CHECK_ARG(env, finalize_cb); CHECK_ARG(env, finalize_cb);
@ -3345,10 +3356,11 @@ napi_status NAPI_CDECL napi_add_finalizer(napi_env env,
#ifdef NAPI_EXPERIMENTAL #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, napi_finalize finalize_cb,
void* finalize_data, void* finalize_data,
void* finalize_hint) { void* finalize_hint) {
napi_env env = const_cast<napi_env>(nogc_env);
CHECK_ENV(env); CHECK_ENV(env);
env->EnqueueFinalizer(v8impl::TrackedFinalizer::New( env->EnqueueFinalizer(v8impl::TrackedFinalizer::New(
env, finalize_cb, finalize_data, finalize_hint)); env, finalize_cb, finalize_data, finalize_hint));
@ -3357,7 +3369,7 @@ napi_status NAPI_CDECL node_api_post_finalizer(napi_env env,
#endif #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 change_in_bytes,
int64_t* adjusted_value) { int64_t* adjusted_value) {
CHECK_ENV(env); CHECK_ENV(env);
@ -3369,10 +3381,11 @@ napi_status NAPI_CDECL napi_adjust_external_memory(napi_env env,
return napi_clear_last_error(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, void* data,
napi_finalize finalize_cb, napi_finalize finalize_cb,
void* finalize_hint) { void* finalize_hint) {
napi_env env = const_cast<napi_env>(nogc_env);
CHECK_ENV(env); CHECK_ENV(env);
v8impl::RefBase* old_data = static_cast<v8impl::RefBase*>(env->instance_data); v8impl::RefBase* old_data = static_cast<v8impl::RefBase*>(env->instance_data);
@ -3388,7 +3401,8 @@ napi_status NAPI_CDECL napi_set_instance_data(napi_env env,
return napi_clear_last_error(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_ENV(env);
CHECK_ARG(env, data); CHECK_ARG(env, data);

View File

@ -4,7 +4,7 @@
#include "js_native_api_types.h" #include "js_native_api_types.h"
#include "js_native_api_v8_internals.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 { namespace v8impl {
@ -172,7 +172,8 @@ struct napi_env__ {
virtual ~napi_env__() = default; 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<napi_env>(nogc_env);
env->last_error.error_code = napi_ok; env->last_error.error_code = napi_ok;
env->last_error.engine_error_code = 0; env->last_error.engine_error_code = 0;
env->last_error.engine_reserved = nullptr; env->last_error.engine_reserved = nullptr;
@ -180,10 +181,11 @@ inline napi_status napi_clear_last_error(napi_env env) {
return napi_ok; 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, napi_status error_code,
uint32_t engine_error_code = 0, uint32_t engine_error_code = 0,
void* engine_reserved = nullptr) { void* engine_reserved = nullptr) {
napi_env env = const_cast<napi_env>(nogc_env);
env->last_error.error_code = error_code; env->last_error.error_code = error_code;
env->last_error.engine_error_code = engine_error_code; env->last_error.engine_error_code = engine_error_code;
env->last_error.engine_reserved = engine_reserved; env->last_error.engine_reserved = engine_reserved;

View File

@ -765,7 +765,7 @@ void NAPI_CDECL napi_module_register(napi_module* mod) {
node::node_module_register(nm); 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, napi_cleanup_hook fun,
void* arg) { void* arg) {
CHECK_ENV(env); CHECK_ENV(env);
@ -776,7 +776,7 @@ napi_status NAPI_CDECL napi_add_env_cleanup_hook(napi_env env,
return napi_ok; 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, napi_cleanup_hook fun,
void* arg) { void* arg) {
CHECK_ENV(env); CHECK_ENV(env);
@ -823,10 +823,11 @@ struct napi_async_cleanup_hook_handle__ {
}; };
napi_status NAPI_CDECL napi_status NAPI_CDECL
napi_add_async_cleanup_hook(napi_env env, napi_add_async_cleanup_hook(node_api_nogc_env nogc_env,
napi_async_cleanup_hook hook, napi_async_cleanup_hook hook,
void* arg, void* arg,
napi_async_cleanup_hook_handle* remove_handle) { napi_async_cleanup_hook_handle* remove_handle) {
napi_env env = const_cast<napi_env>(nogc_env);
CHECK_ENV(env); CHECK_ENV(env);
CHECK_ARG(env, hook); CHECK_ARG(env, hook);
@ -1037,12 +1038,14 @@ napi_status NAPI_CDECL napi_create_buffer(napi_env env,
return GET_RETURN_STATUS(env); return GET_RETURN_STATUS(env);
} }
napi_status NAPI_CDECL napi_create_external_buffer(napi_env env, napi_status NAPI_CDECL
size_t length, napi_create_external_buffer(napi_env env,
void* data, size_t length,
napi_finalize finalize_cb, void* data,
void* finalize_hint, node_api_nogc_finalize nogc_finalize_cb,
napi_value* result) { void* finalize_hint,
napi_value* result) {
napi_finalize finalize_cb = reinterpret_cast<napi_finalize>(nogc_finalize_cb);
NAPI_PREAMBLE(env); NAPI_PREAMBLE(env);
CHECK_ARG(env, result); CHECK_ARG(env, result);
@ -1126,7 +1129,7 @@ napi_status NAPI_CDECL napi_get_buffer_info(napi_env env,
return napi_clear_last_error(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) { const napi_node_version** result) {
CHECK_ENV(env); CHECK_ENV(env);
CHECK_ARG(env, result); CHECK_ARG(env, result);
@ -1270,14 +1273,16 @@ napi_status NAPI_CDECL napi_delete_async_work(napi_env env,
return napi_clear_last_error(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<napi_env>(nogc_env);
CHECK_ENV(env); CHECK_ENV(env);
CHECK_ARG(env, loop); CHECK_ARG(env, loop);
*loop = reinterpret_cast<node_napi_env>(env)->node_env()->event_loop(); *loop = reinterpret_cast<node_napi_env>(env)->node_env()->event_loop();
return napi_clear_last_error(env); 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) { napi_async_work work) {
CHECK_ENV(env); CHECK_ENV(env);
CHECK_ARG(env, work); CHECK_ARG(env, work);
@ -1292,7 +1297,7 @@ napi_status NAPI_CDECL napi_queue_async_work(napi_env env,
return napi_clear_last_error(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) { napi_async_work work) {
CHECK_ENV(env); CHECK_ENV(env);
CHECK_ARG(env, work); CHECK_ARG(env, work);
@ -1312,10 +1317,12 @@ napi_create_threadsafe_function(napi_env env,
size_t max_queue_size, size_t max_queue_size,
size_t initial_thread_count, size_t initial_thread_count,
void* thread_finalize_data, void* thread_finalize_data,
napi_finalize thread_finalize_cb, node_api_nogc_finalize nogc_thread_finalize_cb,
void* context, void* context,
napi_threadsafe_function_call_js call_js_cb, napi_threadsafe_function_call_js call_js_cb,
napi_threadsafe_function* result) { napi_threadsafe_function* result) {
napi_finalize thread_finalize_cb =
reinterpret_cast<napi_finalize>(nogc_thread_finalize_cb);
CHECK_ENV_NOT_IN_GC(env); CHECK_ENV_NOT_IN_GC(env);
CHECK_ARG(env, async_resource_name); CHECK_ARG(env, async_resource_name);
RETURN_STATUS_IF_FALSE(env, initial_thread_count > 0, napi_invalid_arg); RETURN_STATUS_IF_FALSE(env, initial_thread_count > 0, napi_invalid_arg);
@ -1397,20 +1404,21 @@ napi_status NAPI_CDECL napi_release_threadsafe_function(
return reinterpret_cast<v8impl::ThreadSafeFunction*>(func)->Release(mode); return reinterpret_cast<v8impl::ThreadSafeFunction*>(func)->Release(mode);
} }
napi_status NAPI_CDECL napi_status NAPI_CDECL napi_unref_threadsafe_function(
napi_unref_threadsafe_function(napi_env env, napi_threadsafe_function func) { node_api_nogc_env env, napi_threadsafe_function func) {
CHECK_NOT_NULL(func); CHECK_NOT_NULL(func);
return reinterpret_cast<v8impl::ThreadSafeFunction*>(func)->Unref(); return reinterpret_cast<v8impl::ThreadSafeFunction*>(func)->Unref();
} }
napi_status NAPI_CDECL napi_status NAPI_CDECL napi_ref_threadsafe_function(
napi_ref_threadsafe_function(napi_env env, napi_threadsafe_function func) { node_api_nogc_env env, napi_threadsafe_function func) {
CHECK_NOT_NULL(func); CHECK_NOT_NULL(func);
return reinterpret_cast<v8impl::ThreadSafeFunction*>(func)->Ref(); return reinterpret_cast<v8impl::ThreadSafeFunction*>(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) { const char** result) {
napi_env env = const_cast<napi_env>(nogc_env);
CHECK_ENV(env); CHECK_ENV(env);
CHECK_ARG(env, result); CHECK_ARG(env, result);

View File

@ -131,7 +131,7 @@ NAPI_EXTERN napi_status NAPI_CDECL
napi_create_external_buffer(napi_env env, napi_create_external_buffer(napi_env env,
size_t length, size_t length,
void* data, void* data,
napi_finalize finalize_cb, node_api_nogc_finalize finalize_cb,
void* finalize_hint, void* finalize_hint,
napi_value* result); napi_value* result);
#endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED #endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED
@ -159,20 +159,20 @@ napi_create_async_work(napi_env env,
napi_async_work* result); napi_async_work* result);
NAPI_EXTERN napi_status NAPI_CDECL napi_delete_async_work(napi_env env, NAPI_EXTERN napi_status NAPI_CDECL napi_delete_async_work(napi_env env,
napi_async_work work); 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_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); napi_async_work work);
// version management // version management
NAPI_EXTERN napi_status NAPI_CDECL 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 #if NAPI_VERSION >= 2
// Return the current libuv event loop for a given environment // Return the current libuv event loop for a given environment
NAPI_EXTERN napi_status NAPI_CDECL 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 #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_EXTERN napi_status NAPI_CDECL napi_fatal_exception(napi_env env,
napi_value err); napi_value err);
NAPI_EXTERN napi_status NAPI_CDECL NAPI_EXTERN napi_status NAPI_CDECL napi_add_env_cleanup_hook(
napi_add_env_cleanup_hook(napi_env env, napi_cleanup_hook fun, void* arg); node_api_nogc_env env, napi_cleanup_hook fun, void* arg);
NAPI_EXTERN napi_status NAPI_CDECL NAPI_EXTERN napi_status NAPI_CDECL napi_remove_env_cleanup_hook(
napi_remove_env_cleanup_hook(napi_env env, napi_cleanup_hook fun, void* arg); node_api_nogc_env env, napi_cleanup_hook fun, void* arg);
NAPI_EXTERN napi_status NAPI_CDECL NAPI_EXTERN napi_status NAPI_CDECL
napi_open_callback_scope(napi_env env, 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 max_queue_size,
size_t initial_thread_count, size_t initial_thread_count,
void* thread_finalize_data, void* thread_finalize_data,
napi_finalize thread_finalize_cb, node_api_nogc_finalize thread_finalize_cb,
void* context, void* context,
napi_threadsafe_function_call_js call_js_cb, napi_threadsafe_function_call_js call_js_cb,
napi_threadsafe_function* result); 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_EXTERN napi_status NAPI_CDECL napi_release_threadsafe_function(
napi_threadsafe_function func, napi_threadsafe_function_release_mode mode); napi_threadsafe_function func, napi_threadsafe_function_release_mode mode);
NAPI_EXTERN napi_status NAPI_CDECL NAPI_EXTERN napi_status NAPI_CDECL napi_unref_threadsafe_function(
napi_unref_threadsafe_function(napi_env env, napi_threadsafe_function func); node_api_nogc_env env, napi_threadsafe_function func);
NAPI_EXTERN napi_status NAPI_CDECL NAPI_EXTERN napi_status NAPI_CDECL napi_ref_threadsafe_function(
napi_ref_threadsafe_function(napi_env env, napi_threadsafe_function func); node_api_nogc_env env, napi_threadsafe_function func);
#endif // NAPI_VERSION >= 4 #endif // NAPI_VERSION >= 4
#if NAPI_VERSION >= 8 #if NAPI_VERSION >= 8
NAPI_EXTERN napi_status NAPI_CDECL 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, napi_async_cleanup_hook hook,
void* arg, void* arg,
napi_async_cleanup_hook_handle* remove_handle); 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 #if NAPI_VERSION >= 9
NAPI_EXTERN napi_status NAPI_CDECL 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 #endif // NAPI_VERSION >= 9

View File

@ -2,6 +2,7 @@
#define JS_NATIVE_API_COMMON_H_ #define JS_NATIVE_API_COMMON_H_
#include <js_native_api.h> #include <js_native_api.h>
#include <stdlib.h> // abort()
// Empty value so that macros here are able to return NULL or void // Empty value so that macros here are able to return NULL or void
#define NODE_API_RETVAL_NOTHING // Intentionally blank #define #define NODE_API_RETVAL_NOTHING // Intentionally blank #define
@ -22,6 +23,19 @@
} \ } \
} while (0) } while (0)
// The nogc 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) \ #define NODE_API_ASSERT_BASE(env, assertion, message, ret_val) \
do { \ do { \
if (!(assertion)) { \ if (!(assertion)) { \
@ -33,6 +47,15 @@
} \ } \
} while (0) } 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. // Returns NULL on failed assertion.
// This is meant to be used inside napi_callback methods. // This is meant to be used inside napi_callback methods.
#define NODE_API_ASSERT(env, assertion, message) \ #define NODE_API_ASSERT(env, assertion, message) \
@ -43,6 +66,9 @@
#define NODE_API_ASSERT_RETURN_VOID(env, assertion, message) \ #define NODE_API_ASSERT_RETURN_VOID(env, assertion, message) \
NODE_API_ASSERT_BASE(env, assertion, message, NODE_API_RETVAL_NOTHING) 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) \ #define NODE_API_CALL_BASE(env, the_call, ret_val) \
do { \ do { \
if ((the_call) != napi_ok) { \ if ((the_call) != napi_ok) { \
@ -51,6 +77,14 @@
} \ } \
} while (0) } 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. // Returns NULL if the_call doesn't return napi_ok.
#define NODE_API_CALL(env, the_call) \ #define NODE_API_CALL(env, the_call) \
NODE_API_CALL_BASE(env, the_call, NULL) NODE_API_CALL_BASE(env, the_call, NULL)
@ -59,6 +93,9 @@
#define NODE_API_CALL_RETURN_VOID(env, the_call) \ #define NODE_API_CALL_RETURN_VOID(env, the_call) \
NODE_API_CALL_BASE(env, the_call, NODE_API_RETVAL_NOTHING) 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) \ #define NODE_API_CHECK_STATUS(the_call) \
do { \ do { \
napi_status status = (the_call); \ napi_status status = (the_call); \

View File

@ -20,6 +20,15 @@ static void Finalize(napi_env env, void* data, void* hint) {
free(ref); free(ref);
} }
static void NogcFinalize(node_api_nogc_env env, void* data, void* hint) {
#ifdef NAPI_EXPERIMENTAL
NODE_API_NOGC_CALL_RETURN_VOID(
env, node_api_post_finalizer(env, Finalize, data, hint));
#else
Finalize(env, data, hint);
#endif
}
static napi_value CreateRef(napi_env env, napi_callback_info info) { static napi_value CreateRef(napi_env env, napi_callback_info info) {
size_t argc = 1; size_t argc = 1;
napi_value cb; 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_CALL(env, napi_typeof(env, cb, &value_type));
NODE_API_ASSERT( NODE_API_ASSERT(
env, value_type == napi_function, "argument must be function"); 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, NogcFinalize, NULL, ref));
return cb; return cb;
} }

View File

@ -11,17 +11,17 @@ typedef struct {
napi_ref js_func; napi_ref js_func;
} FinalizerData; } FinalizerData;
static void finalizerOnlyCallback(napi_env env, static void finalizerOnlyCallback(node_api_nogc_env env,
void* finalize_data, void* finalize_data,
void* finalize_hint) { void* finalize_hint) {
FinalizerData* data = (FinalizerData*)finalize_data; FinalizerData* data = (FinalizerData*)finalize_data;
int32_t count = ++data->finalize_count; int32_t count = ++data->finalize_count;
// It is safe to access instance data // It is safe to access instance data
NODE_API_CALL_RETURN_VOID(env, napi_get_instance_data(env, (void**)&data)); NODE_API_NOGC_CALL_RETURN_VOID(env,
NODE_API_ASSERT_RETURN_VOID(env, napi_get_instance_data(env, (void**)&data));
count = data->finalize_count, NODE_API_NOGC_ASSERT_RETURN_VOID(count = data->finalize_count,
"Expected to be the same FinalizerData"); "Expected to be the same FinalizerData");
} }
static void finalizerCallingJSCallback(napi_env env, static void finalizerCallingJSCallback(napi_env env,
@ -40,18 +40,20 @@ static void finalizerCallingJSCallback(napi_env env,
} }
// Schedule async finalizer to run JavaScript-touching code. // 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_data,
void* finalize_hint) { void* finalize_hint) {
NODE_API_CALL_RETURN_VOID( NODE_API_NOGC_CALL_RETURN_VOID(
env, env,
node_api_post_finalizer( node_api_post_finalizer(
env, finalizerCallingJSCallback, finalize_data, finalize_hint)); env, finalizerCallingJSCallback, finalize_data, finalize_hint));
} }
static void finalizerWithFailedJSCallback(napi_env env, static void finalizerWithFailedJSCallback(node_api_nogc_env nogc_env,
void* finalize_data, void* finalize_data,
void* finalize_hint) { void* finalize_hint) {
// Intentionally cast to a napi_env to test the fatal failure.
napi_env env = (napi_env)nogc_env;
napi_value obj; napi_value obj;
FinalizerData* data = (FinalizerData*)finalize_data; FinalizerData* data = (FinalizerData*)finalize_data;
++data->finalize_count; ++data->finalize_count;

View File

@ -88,7 +88,7 @@ static napi_value TestTwoByteImpl(napi_env env,
return output; 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); free(data);
} }

View File

@ -4,12 +4,12 @@
static uint32_t finalizeCount = 0; static uint32_t finalizeCount = 0;
static void FreeData(napi_env env, void* data, void* hint) { static void FreeData(node_api_nogc_env env, void* data, void* hint) {
NODE_API_ASSERT_RETURN_VOID(env, data != NULL, "Expects non-NULL data."); NODE_API_NOGC_ASSERT_RETURN_VOID(data != NULL, "Expects non-NULL data.");
free(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; ++finalizeCount;
} }
@ -61,7 +61,7 @@ static napi_value ToUInt32Value(napi_env env, uint32_t value) {
static napi_status InitRefArray(napi_env env) { static napi_status InitRefArray(napi_env env) {
// valueRefs array has one entry per napi_valuetype // valueRefs array has one entry per napi_valuetype
napi_ref* valueRefs = malloc(sizeof(napi_ref) * ((int)napi_bigint + 1)); 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) { static napi_value CreateExternal(napi_env env, napi_callback_info info) {

View File

@ -16,6 +16,18 @@ static void ThreadSafeFunctionFinalize(napi_env env,
NODE_API_CALL_RETURN_VOID(env, napi_delete_reference(env, js_func_ref)); NODE_API_CALL_RETURN_VOID(env, napi_delete_reference(env, js_func_ref));
} }
static void ThreadSafeFunctionNogcFinalize(node_api_nogc_env env,
void* data,
void* hint) {
#ifdef NAPI_EXPERIMENTAL
NODE_API_NOGC_CALL_RETURN_VOID(
env,
node_api_post_finalizer(env, ThreadSafeFunctionFinalize, data, hint));
#else
ThreadSafeFunctionFinalize(env, data, hint);
#endif
}
// Testing calling into JavaScript // Testing calling into JavaScript
static napi_value CallIntoModule(napi_env env, napi_callback_info info) { static napi_value CallIntoModule(napi_env env, napi_callback_info info) {
size_t argc = 4; size_t argc = 4;
@ -34,7 +46,7 @@ static napi_value CallIntoModule(napi_env env, napi_callback_info info) {
0, 0,
1, 1,
finalize_func, finalize_func,
ThreadSafeFunctionFinalize, ThreadSafeFunctionNogcFinalize,
NULL, NULL,
NULL, NULL,
&tsfn)); &tsfn));

View File

@ -58,6 +58,8 @@ const jobs = [];
for await (const dirent of await fs.opendir(directory)) { for await (const dirent of await fs.opendir(directory)) {
if (dirent.isDirectory()) { if (dirent.isDirectory()) {
jobs.push(() => buildAddon(path.join(directory, dirent.name))); jobs.push(() => buildAddon(path.join(directory, dirent.name)));
} else if (dirent.isFile() && dirent.name === 'binding.gyp') {
jobs.push(() => buildAddon(directory));
} }
} }
await parallel(jobs, parallelization); await parallel(jobs, parallelization);