Skip to content

Commit 5c2ffc4

Browse files
node-api: segregate pure and non-pure APIs via type system
We define a new type called `node_api_pure_env` as the `const` version of `napi_env` and `node_api_pure_finalize` as a variant of `napi_finalize` that accepts a `node_api_pure_env` as its first argument. We then modify those APIs which do not affect GC state as accepting a `node_api_pure_env`. APIs accepting finalizer callbacks are modified to accept `node_api_pure_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_pure_finalize` callback. In keeping with the process of introducing new Node-APIs, this feature is guarded by `NAPI_EXPERIMENTAL`. Additionally, and since this feature modifies APIs already marked as stable, it is additionally guared by `NODE_API_EXPERIMENTAL_PURE_ENV`, so as to provide a further buffer to adoption. Nevertheless, both guards must be removed upon releasing a new version of Node-API.
1 parent f73650e commit 5c2ffc4

File tree

24 files changed

+289
-151
lines changed

24 files changed

+289
-151
lines changed

doc/contributing/adding-new-napi-api.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,12 @@ Node-API.
4747
to the decision to take an API out of experimental status.
4848
* The API **must** be implemented in a Node.js implementation with an
4949
alternate VM.
50+
51+
Since the adoption of the policy whereby moving to a later version of Node-API
52+
from an earlier version may entail rework of existing code, it is possible to
53+
introduce modifications to already-released Node-APIs, as long as the
54+
modifications affect neither the ABI nor the API of earlier versions. Such
55+
modifications **must** be guarded, in addition to the above-mentioned
56+
compile-time flag, by a further compile-time flag whose name reflects the
57+
nature of the modification being made to existing APIs. This provides a buffer
58+
to adoption above and beyond the one provided by the initial compile-time flag.

doc/contributing/releases-node-api.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,9 @@ define guards on the declaration of the new Node-API. Check for these guards
8484
with:
8585
8686
```bash
87-
grep NAPI_EXPERIMENTAL src/js_native_api{_types,}.h src/node_api{_types,}.h
87+
grep \
88+
-E \
89+
N(ODE_)?API_EXPERIMENTAL src/js_native_api{_types,}.h src/node_api{_types,}.h
8890
```
8991

9092
and update the define version guards with the release version:
@@ -100,6 +102,9 @@ and update the define version guards with the release version:
100102
+ #endif // NAPI_VERSION >= 10
101103
```
102104
105+
Remove any additional `NODE_API_EXPERIMENTAL_*` guards along with
106+
`NAPI_EXPERIMENTAL`.
107+
103108
Also, update the Node-API version value of the `napi_get_version` test in
104109
`test/js-native-api/test_general/test.js` with the release version `x`:
105110
@@ -128,7 +133,7 @@ commits should already include `NAPI_EXPERIMENTAL` definition for the tests.
128133
Check for these definitions with:
129134

130135
```bash
131-
grep NAPI_EXPERIMENTAL test/node-api/*/{*.{h,c},binding.gyp} test/js-native-api/*/{*.{h,c},binding.gyp}
136+
grep -E N(ODE_)?API_EXPERIMENTAL test/node-api/*/{*.{h,c},binding.gyp} test/js-native-api/*/{*.{h,c},binding.gyp}
132137
```
133138

134139
and substitute the `NAPI_EXPERIMENTAL` with the release version
@@ -139,6 +144,8 @@ and substitute the `NAPI_EXPERIMENTAL` with the release version
139144
+ #define NAPI_VERSION 10
140145
```
141146

147+
Remove any `NODE_API_EXPERIMENTAL_*` flags.
148+
142149
#### Step 4. Update document
143150

144151
If this release includes new Node-APIs that were first released in this

src/js_native_api.h

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,8 @@
4949

5050
EXTERN_C_START
5151

52-
NAPI_EXTERN napi_status NAPI_CDECL
53-
napi_get_last_error_info(napi_env env, const napi_extended_error_info** result);
52+
NAPI_EXTERN napi_status NAPI_CDECL napi_get_last_error_info(
53+
node_api_pure_env env, const napi_extended_error_info** result);
5454

5555
// Getters for defined singletons
5656
NAPI_EXTERN napi_status NAPI_CDECL napi_get_undefined(napi_env env,
@@ -97,15 +97,15 @@ NAPI_EXTERN napi_status NAPI_CDECL
9797
node_api_create_external_string_latin1(napi_env env,
9898
char* str,
9999
size_t length,
100-
napi_finalize finalize_callback,
100+
node_api_pure_finalize finalize_callback,
101101
void* finalize_hint,
102102
napi_value* result,
103103
bool* copied);
104104
NAPI_EXTERN napi_status NAPI_CDECL
105105
node_api_create_external_string_utf16(napi_env env,
106106
char16_t* str,
107107
size_t length,
108-
napi_finalize finalize_callback,
108+
node_api_pure_finalize finalize_callback,
109109
void* finalize_hint,
110110
napi_value* result,
111111
bool* copied);
@@ -289,7 +289,7 @@ NAPI_EXTERN napi_status NAPI_CDECL napi_instanceof(napi_env env,
289289

290290
// Gets all callback info in a single call. (Ugly, but faster.)
291291
NAPI_EXTERN napi_status NAPI_CDECL napi_get_cb_info(
292-
napi_env env, // [in] NAPI environment handle
292+
node_api_pure_env env, // [in] NAPI environment handle
293293
napi_callback_info cbinfo, // [in] Opaque callback-info handle
294294
size_t* argc, // [in-out] Specifies the size of the provided argv array
295295
// and receives the actual count of args.
@@ -313,7 +313,7 @@ napi_define_class(napi_env env,
313313
NAPI_EXTERN napi_status NAPI_CDECL napi_wrap(napi_env env,
314314
napi_value js_object,
315315
void* native_object,
316-
napi_finalize finalize_cb,
316+
node_api_pure_finalize finalize_cb,
317317
void* finalize_hint,
318318
napi_ref* result);
319319
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,
325325
NAPI_EXTERN napi_status NAPI_CDECL
326326
napi_create_external(napi_env env,
327327
void* data,
328-
napi_finalize finalize_cb,
328+
node_api_pure_finalize finalize_cb,
329329
void* finalize_hint,
330330
napi_value* result);
331331
NAPI_EXTERN napi_status NAPI_CDECL napi_get_value_external(napi_env env,
@@ -424,7 +424,7 @@ NAPI_EXTERN napi_status NAPI_CDECL
424424
napi_create_external_arraybuffer(napi_env env,
425425
void* external_data,
426426
size_t byte_length,
427-
napi_finalize finalize_cb,
427+
node_api_pure_finalize finalize_cb,
428428
void* finalize_hint,
429429
napi_value* result);
430430
#endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED
@@ -466,7 +466,7 @@ napi_get_dataview_info(napi_env env,
466466
size_t* byte_offset);
467467

468468
// version management
469-
NAPI_EXTERN napi_status NAPI_CDECL napi_get_version(napi_env env,
469+
NAPI_EXTERN napi_status NAPI_CDECL napi_get_version(node_api_pure_env env,
470470
uint32_t* result);
471471

472472
// Promises
@@ -490,7 +490,7 @@ NAPI_EXTERN napi_status NAPI_CDECL napi_run_script(napi_env env,
490490

491491
// Memory management
492492
NAPI_EXTERN napi_status NAPI_CDECL napi_adjust_external_memory(
493-
napi_env env, int64_t change_in_bytes, int64_t* adjusted_value);
493+
node_api_pure_env env, int64_t change_in_bytes, int64_t* adjusted_value);
494494

495495
#if NAPI_VERSION >= 5
496496

@@ -508,19 +508,20 @@ NAPI_EXTERN napi_status NAPI_CDECL napi_get_date_value(napi_env env,
508508
double* result);
509509

510510
// Add finalizer for pointer
511-
NAPI_EXTERN napi_status NAPI_CDECL napi_add_finalizer(napi_env env,
512-
napi_value js_object,
513-
void* finalize_data,
514-
napi_finalize finalize_cb,
515-
void* finalize_hint,
516-
napi_ref* result);
511+
NAPI_EXTERN napi_status NAPI_CDECL
512+
napi_add_finalizer(napi_env env,
513+
napi_value js_object,
514+
void* finalize_data,
515+
node_api_pure_finalize finalize_cb,
516+
void* finalize_hint,
517+
napi_ref* result);
517518

518519
#endif // NAPI_VERSION >= 5
519520

520521
#ifdef NAPI_EXPERIMENTAL
521522

522523
NAPI_EXTERN napi_status NAPI_CDECL
523-
node_api_post_finalizer(napi_env env,
524+
node_api_post_finalizer(node_api_pure_env env,
524525
napi_finalize finalize_cb,
525526
void* finalize_data,
526527
void* finalize_hint);
@@ -564,10 +565,13 @@ napi_get_all_property_names(napi_env env,
564565
napi_value* result);
565566

566567
// Instance data
567-
NAPI_EXTERN napi_status NAPI_CDECL napi_set_instance_data(
568-
napi_env env, void* data, napi_finalize finalize_cb, void* finalize_hint);
568+
NAPI_EXTERN napi_status NAPI_CDECL
569+
napi_set_instance_data(node_api_pure_env env,
570+
void* data,
571+
node_api_pure_finalize finalize_cb,
572+
void* finalize_hint);
569573

570-
NAPI_EXTERN napi_status NAPI_CDECL napi_get_instance_data(napi_env env,
574+
NAPI_EXTERN napi_status NAPI_CDECL napi_get_instance_data(node_api_pure_env env,
571575
void** data);
572576
#endif // NAPI_VERSION >= 6
573577

src/js_native_api_types.h

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,32 @@ typedef uint16_t char16_t;
2222
// JSVM API types are all opaque pointers for ABI stability
2323
// typedef undefined structs instead of void* for compile time type safety
2424
typedef struct napi_env__* napi_env;
25+
26+
// We need to mark APIs which are "pure", meaning that they do not interact
27+
// with the JS engine, and can therefore be called synchronously from a
28+
// finalizer that itself runs synchronously during garbage collection. Such
29+
// "pure" APIs can receive either a `napi_env` or a `node_api_pure_env` as
30+
// their first parameter, because we should be able to also call them during
31+
// normal, non-garbage-collecting operations, whereas "non-pure" APIs can only
32+
// receive a `napi_env` as their first parameter, because we must not call them
33+
// during garbage collection. In lieu of inheritance, we use the properties of
34+
// the const qualifier to accomplish this, because both a const and a non-const
35+
// value can be passed to an API expecting a const value, but only a non-const
36+
// value can be passed to an API expecting a non-const value.
37+
//
38+
// In conjunction with appropriate CFLAGS to warn us if we're passing a const
39+
// (pure) environment into an API that expects a non-const (non-pure)
40+
// environment, and the definition of pure finalizer function pointer types
41+
// below, which receive a pure environment as their first parameter, and can
42+
// thus only call pure APIs (unless the user explicitly casts the environment),
43+
// we achieve the ability to ensure at compile time that we do not call non-
44+
// pure APIs from a synchronous (pure) finalizer.
45+
#if defined(NAPI_EXPERIMENTAL) && defined(NODE_API_EXPERIMENTAL_PURE_ENV)
46+
typedef const struct napi_env__* node_api_pure_env;
47+
#else
48+
typedef struct napi_env__* node_api_pure_env;
49+
#endif // NAPI_EXPERIMENTAL && NODE_API_EXPERIMENTAL_PURE_ENV
50+
2551
typedef struct napi_value__* napi_value;
2652
typedef struct napi_ref__* napi_ref;
2753
typedef struct napi_handle_scope__* napi_handle_scope;
@@ -115,6 +141,13 @@ typedef napi_value(NAPI_CDECL* napi_callback)(napi_env env,
115141
typedef void(NAPI_CDECL* napi_finalize)(napi_env env,
116142
void* finalize_data,
117143
void* finalize_hint);
144+
#if defined(NAPI_EXPERIMENTAL) && defined(NODE_API_EXPERIMENTAL_PURE_ENV)
145+
typedef void(NAPI_CDECL* node_api_pure_finalize)(node_api_pure_env env,
146+
void* finalize_data,
147+
void* finalize_hint);
148+
#else
149+
typedef napi_finalize node_api_pure_finalize;
150+
#endif // NAPI_EXPERIMENTAL && NODE_API_EXPERIMENTAL_PURE_ENV
118151

119152
typedef struct {
120153
// One of utf8name or name should be NULL.

0 commit comments

Comments
 (0)