Skip to content

Commit f489c67

Browse files
vmoroztargos
authored andcommitted
node-api: get Node API version used by addon
PR-URL: #45715 Reviewed-By: Gabriel Schulhof <gabrielschulhof@gmail.com> Reviewed-By: Michael Dawson <midawson@redhat.com> Reviewed-By: Chengzhong Wu <legendecas@gmail.com>
1 parent a4d6543 commit f489c67

17 files changed

+620
-73
lines changed

doc/api/n-api.md

+38-23
Original file line numberDiff line numberDiff line change
@@ -1639,25 +1639,36 @@ If it is called more than once an error will be returned.
16391639

16401640
This API can be called even if there is a pending JavaScript exception.
16411641

1642-
### References to objects with a lifespan longer than that of the native method
1642+
### References to values with a lifespan longer than that of the native method
16431643

1644-
In some cases an addon will need to be able to create and reference objects
1644+
In some cases, an addon will need to be able to create and reference values
16451645
with a lifespan longer than that of a single native method invocation. For
16461646
example, to create a constructor and later use that constructor
1647-
in a request to creates instances, it must be possible to reference
1647+
in a request to create instances, it must be possible to reference
16481648
the constructor object across many different instance creation requests. This
16491649
would not be possible with a normal handle returned as a `napi_value` as
16501650
described in the earlier section. The lifespan of a normal handle is
16511651
managed by scopes and all scopes must be closed before the end of a native
16521652
method.
16531653

1654-
Node-API provides methods to create persistent references to an object.
1655-
Each persistent reference has an associated count with a value of 0
1656-
or higher. The count determines if the reference will keep
1657-
the corresponding object live. References with a count of 0 do not
1658-
prevent the object from being collected and are often called 'weak'
1659-
references. Any count greater than 0 will prevent the object
1660-
from being collected.
1654+
Node-API provides methods for creating persistent references to values.
1655+
Each reference has an associated count with a value of 0 or higher,
1656+
which determines whether the reference will keep the corresponding value alive.
1657+
References with a count of 0 do not prevent values from being collected.
1658+
Values of object (object, function, external) and symbol types are becoming
1659+
'weak' references and can still be accessed while they are not collected.
1660+
Values of other types are released when the count becomes 0
1661+
and cannot be accessed from the reference any more.
1662+
Any count greater than 0 will prevent the values from being collected.
1663+
1664+
Symbol values have different flavors. The true weak reference behavior is
1665+
only supported by local symbols created with the `Symbol()` constructor call.
1666+
Globally registered symbols created with the `Symbol.for()` call remain
1667+
always strong references because the garbage collector does not collect them.
1668+
The same is true for well-known symbols such as `Symbol.iterator`. They are
1669+
also never collected by the garbage collector. JavaScript's `WeakRef` and
1670+
`WeakMap` types return an error when registered symbols are used,
1671+
but they succeed for local and well-known symbols.
16611672

16621673
References can be created with an initial reference count. The count can
16631674
then be modified through [`napi_reference_ref`][] and
@@ -1668,6 +1679,11 @@ will return `NULL` for the returned `napi_value`. An attempt to call
16681679
[`napi_reference_ref`][] for a reference whose object has been collected
16691680
results in an error.
16701681

1682+
Node-API versions 8 and earlier only allow references to be created for a
1683+
limited set of value types, including object, external, function, and symbol.
1684+
However, in newer Node-API versions, references can be created for any
1685+
value type.
1686+
16711687
References must be deleted once they are no longer required by the addon. When
16721688
a reference is deleted, it will no longer prevent the corresponding object from
16731689
being collected. Failure to delete a persistent reference results in
@@ -1700,15 +1716,18 @@ NAPI_EXTERN napi_status napi_create_reference(napi_env env,
17001716
```
17011717

17021718
* `[in] env`: The environment that the API is invoked under.
1703-
* `[in] value`: `napi_value` representing the `Object` to which we want a
1704-
reference.
1719+
* `[in] value`: The `napi_value` for which a reference is being created.
17051720
* `[in] initial_refcount`: Initial reference count for the new reference.
17061721
* `[out] result`: `napi_ref` pointing to the new reference.
17071722

17081723
Returns `napi_ok` if the API succeeded.
17091724

17101725
This API creates a new reference with the specified reference count
1711-
to the `Object` passed in.
1726+
to the value passed in.
1727+
1728+
In Node-API version 8 and earlier, a reference could only be created for
1729+
object, function, external, and symbol value types. However, in newer Node-API
1730+
versions, a reference can be created for any value type.
17121731

17131732
#### `napi_delete_reference`
17141733

@@ -1787,18 +1806,15 @@ NAPI_EXTERN napi_status napi_get_reference_value(napi_env env,
17871806
napi_value* result);
17881807
```
17891808

1790-
the `napi_value passed` in or out of these methods is a handle to the
1791-
object to which the reference is related.
1792-
17931809
* `[in] env`: The environment that the API is invoked under.
1794-
* `[in] ref`: `napi_ref` for which we requesting the corresponding `Object`.
1795-
* `[out] result`: The `napi_value` for the `Object` referenced by the
1796-
`napi_ref`.
1810+
* `[in] ref`: The `napi_ref` for which the corresponding value is
1811+
being requested.
1812+
* `[out] result`: The `napi_value` referenced by the `napi_ref`.
17971813

17981814
Returns `napi_ok` if the API succeeded.
17991815

18001816
If still valid, this API returns the `napi_value` representing the
1801-
JavaScript `Object` associated with the `napi_ref`. Otherwise, result
1817+
JavaScript value associated with the `napi_ref`. Otherwise, result
18021818
will be `NULL`.
18031819

18041820
### Cleanup on exit of the current Node.js environment
@@ -5069,9 +5085,8 @@ napi_status napi_define_class(napi_env env,
50695085
```
50705086

50715087
* `[in] env`: The environment that the API is invoked under.
5072-
* `[in] utf8name`: Name of the JavaScript constructor function; When wrapping a
5073-
C++ class, we recommend for clarity that this name be the same as that of
5074-
the C++ class.
5088+
* `[in] utf8name`: Name of the JavaScript constructor function. For clarity,
5089+
it is recommended to use the C++ class name when wrapping a C++ class.
50755090
* `[in] length`: The length of the `utf8name` in bytes, or `NAPI_AUTO_LENGTH`
50765091
if it is null-terminated.
50775092
* `[in] constructor`: Callback function that handles constructing instances

src/api/environment.cc

+11-19
Original file line numberDiff line numberDiff line change
@@ -872,26 +872,18 @@ void AddLinkedBinding(Environment* env,
872872

873873
void AddLinkedBinding(Environment* env,
874874
const char* name,
875-
napi_addon_register_func fn) {
875+
napi_addon_register_func fn,
876+
int32_t module_api_version) {
876877
node_module mod = {
877-
-1,
878-
NM_F_LINKED,
879-
nullptr, // nm_dso_handle
880-
nullptr, // nm_filename
881-
nullptr, // nm_register_func
882-
[](v8::Local<v8::Object> exports,
883-
v8::Local<v8::Value> module,
884-
v8::Local<v8::Context> context,
885-
void* priv) {
886-
napi_module_register_by_symbol(
887-
exports,
888-
module,
889-
context,
890-
reinterpret_cast<napi_addon_register_func>(priv));
891-
},
892-
name,
893-
reinterpret_cast<void*>(fn),
894-
nullptr // nm_link
878+
-1, // nm_version for Node-API
879+
NM_F_LINKED, // nm_flags
880+
nullptr, // nm_dso_handle
881+
nullptr, // nm_filename
882+
nullptr, // nm_register_func
883+
get_node_api_context_register_func(env, name, module_api_version),
884+
name, // nm_modname
885+
reinterpret_cast<void*>(fn), // nm_priv
886+
nullptr // nm_link
895887
};
896888
AddLinkedBinding(env, mod);
897889
}

src/js_native_api_v8.cc

+25-6
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,18 @@ inline napi_status Wrap(napi_env env,
457457
return GET_RETURN_STATUS(env);
458458
}
459459

460+
// In JavaScript, weak references can be created for object types (Object,
461+
// Function, and external Object) and for local symbols that are created with
462+
// the `Symbol` function call. Global symbols created with the `Symbol.for`
463+
// method cannot be weak references because they are never collected.
464+
//
465+
// Currently, V8 has no API to detect if a symbol is local or global.
466+
// Until we have a V8 API for it, we consider that all symbols can be weak.
467+
// This matches the current Node-API behavior.
468+
inline bool CanBeHeldWeakly(v8::Local<v8::Value> value) {
469+
return value->IsObject() || value->IsSymbol();
470+
}
471+
460472
} // end of anonymous namespace
461473

462474
void Finalizer::ResetFinalizer() {
@@ -551,7 +563,8 @@ void RefBase::Finalize() {
551563
template <typename... Args>
552564
Reference::Reference(napi_env env, v8::Local<v8::Value> value, Args&&... args)
553565
: RefBase(env, std::forward<Args>(args)...),
554-
persistent_(env->isolate, value) {
566+
persistent_(env->isolate, value),
567+
can_be_weak_(CanBeHeldWeakly(value)) {
555568
if (RefCount() == 0) {
556569
SetWeak();
557570
}
@@ -585,7 +598,7 @@ uint32_t Reference::Ref() {
585598
return 0;
586599
}
587600
uint32_t refcount = RefBase::Ref();
588-
if (refcount == 1) {
601+
if (refcount == 1 && can_be_weak_) {
589602
persistent_.ClearWeak();
590603
}
591604
return refcount;
@@ -625,7 +638,11 @@ void Reference::Finalize() {
625638
// Mark the reference as weak and eligible for collection
626639
// by the gc.
627640
void Reference::SetWeak() {
628-
persistent_.SetWeak(this, WeakCallback, v8::WeakCallbackType::kParameter);
641+
if (can_be_weak_) {
642+
persistent_.SetWeak(this, WeakCallback, v8::WeakCallbackType::kParameter);
643+
} else {
644+
persistent_.Reset();
645+
}
629646
}
630647

631648
// The N-API finalizer callback may make calls into the engine. V8's heap is
@@ -2419,9 +2436,11 @@ napi_status NAPI_CDECL napi_create_reference(napi_env env,
24192436
CHECK_ARG(env, result);
24202437

24212438
v8::Local<v8::Value> v8_value = v8impl::V8LocalValueFromJsValue(value);
2422-
if (!(v8_value->IsObject() || v8_value->IsFunction() ||
2423-
v8_value->IsSymbol())) {
2424-
return napi_set_last_error(env, napi_invalid_arg);
2439+
if (env->module_api_version <= 8) {
2440+
if (!(v8_value->IsObject() || v8_value->IsFunction() ||
2441+
v8_value->IsSymbol())) {
2442+
return napi_set_last_error(env, napi_invalid_arg);
2443+
}
24252444
}
24262445

24272446
v8impl::Reference* reference = v8impl::Reference::New(

src/js_native_api_v8.h

+7-2
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,11 @@ class Finalizer;
5151
} // end of namespace v8impl
5252

5353
struct napi_env__ {
54-
explicit napi_env__(v8::Local<v8::Context> context)
55-
: isolate(context->GetIsolate()), context_persistent(isolate, context) {
54+
explicit napi_env__(v8::Local<v8::Context> context,
55+
int32_t module_api_version)
56+
: isolate(context->GetIsolate()),
57+
context_persistent(isolate, context),
58+
module_api_version(module_api_version) {
5659
napi_clear_last_error(this);
5760
}
5861

@@ -144,6 +147,7 @@ struct napi_env__ {
144147
int open_callback_scopes = 0;
145148
int refs = 1;
146149
void* instance_data = nullptr;
150+
int32_t module_api_version = NODE_API_DEFAULT_MODULE_API_VERSION;
147151

148152
protected:
149153
// Should not be deleted directly. Delete with `napi_env__::DeleteMe()`
@@ -419,6 +423,7 @@ class Reference : public RefBase {
419423
void SetWeak();
420424

421425
v8impl::Persistent<v8::Value> persistent_;
426+
bool can_be_weak_;
422427
};
423428

424429
} // end of namespace v8impl

src/node.h

+5-3
Original file line numberDiff line numberDiff line change
@@ -1237,9 +1237,11 @@ NODE_EXTERN void AddLinkedBinding(Environment* env,
12371237
const char* name,
12381238
addon_context_register_func fn,
12391239
void* priv);
1240-
NODE_EXTERN void AddLinkedBinding(Environment* env,
1241-
const char* name,
1242-
napi_addon_register_func fn);
1240+
NODE_EXTERN void AddLinkedBinding(
1241+
Environment* env,
1242+
const char* name,
1243+
napi_addon_register_func fn,
1244+
int32_t module_api_version = NODE_API_DEFAULT_MODULE_API_VERSION);
12431245

12441246
/* Registers a callback with the passed-in Environment instance. The callback
12451247
* is called after the event loop exits, but before the VM is disposed.

src/node_api.cc

+70-6
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,9 @@
2020
#include <memory>
2121

2222
node_napi_env__::node_napi_env__(v8::Local<v8::Context> context,
23-
const std::string& module_filename)
24-
: napi_env__(context), filename(module_filename) {
23+
const std::string& module_filename,
24+
int32_t module_api_version)
25+
: napi_env__(context, module_api_version), filename(module_filename) {
2526
CHECK_NOT_NULL(node_env());
2627
}
2728

@@ -151,11 +152,36 @@ class BufferFinalizer : private Finalizer {
151152
~BufferFinalizer() { env_->Unref(); }
152153
};
153154

155+
void ThrowNodeApiVersionError(node::Environment* node_env,
156+
const char* module_name,
157+
int32_t module_api_version) {
158+
std::string error_message;
159+
error_message += module_name;
160+
error_message += " requires Node-API version ";
161+
error_message += std::to_string(module_api_version);
162+
error_message += ", but this version of Node.js only supports version ";
163+
error_message += NODE_STRINGIFY(NAPI_VERSION) " add-ons.";
164+
node_env->ThrowError(error_message.c_str());
165+
}
166+
154167
inline napi_env NewEnv(v8::Local<v8::Context> context,
155-
const std::string& module_filename) {
168+
const std::string& module_filename,
169+
int32_t module_api_version) {
156170
node_napi_env result;
157171

158-
result = new node_napi_env__(context, module_filename);
172+
// Validate module_api_version.
173+
if (module_api_version < NODE_API_DEFAULT_MODULE_API_VERSION) {
174+
module_api_version = NODE_API_DEFAULT_MODULE_API_VERSION;
175+
} else if (module_api_version > NAPI_VERSION &&
176+
module_api_version != NAPI_VERSION_EXPERIMENTAL) {
177+
node::Environment* node_env = node::Environment::GetCurrent(context);
178+
CHECK_NOT_NULL(node_env);
179+
ThrowNodeApiVersionError(
180+
node_env, module_filename.c_str(), module_api_version);
181+
return nullptr;
182+
}
183+
184+
result = new node_napi_env__(context, module_filename, module_api_version);
159185
// TODO(addaleax): There was previously code that tried to delete the
160186
// napi_env when its v8::Context was garbage collected;
161187
// However, as long as N-API addons using this napi_env are in place,
@@ -623,10 +649,48 @@ static void napi_module_register_cb(v8::Local<v8::Object> exports,
623649
static_cast<const napi_module*>(priv)->nm_register_func);
624650
}
625651

652+
template <int32_t module_api_version>
653+
static void node_api_context_register_func(v8::Local<v8::Object> exports,
654+
v8::Local<v8::Value> module,
655+
v8::Local<v8::Context> context,
656+
void* priv) {
657+
napi_module_register_by_symbol(
658+
exports,
659+
module,
660+
context,
661+
reinterpret_cast<napi_addon_register_func>(priv),
662+
module_api_version);
663+
}
664+
665+
// This function must be augmented for each new Node API version.
666+
// The key role of this function is to encode module_api_version in the function
667+
// pointer. We are not going to have many Node API versions and having one
668+
// function per version is relatively cheap. It avoids dynamic memory
669+
// allocations or implementing more expensive changes to module registration.
670+
// Currently AddLinkedBinding is the only user of this function.
671+
node::addon_context_register_func get_node_api_context_register_func(
672+
node::Environment* node_env,
673+
const char* module_name,
674+
int32_t module_api_version) {
675+
static_assert(
676+
NAPI_VERSION == 8,
677+
"New version of Node-API requires adding another else-if statement below "
678+
"for the new version and updating this assert condition.");
679+
if (module_api_version <= NODE_API_DEFAULT_MODULE_API_VERSION) {
680+
return node_api_context_register_func<NODE_API_DEFAULT_MODULE_API_VERSION>;
681+
} else if (module_api_version == NAPI_VERSION_EXPERIMENTAL) {
682+
return node_api_context_register_func<NAPI_VERSION_EXPERIMENTAL>;
683+
} else {
684+
v8impl::ThrowNodeApiVersionError(node_env, module_name, module_api_version);
685+
return nullptr;
686+
}
687+
}
688+
626689
void napi_module_register_by_symbol(v8::Local<v8::Object> exports,
627690
v8::Local<v8::Value> module,
628691
v8::Local<v8::Context> context,
629-
napi_addon_register_func init) {
692+
napi_addon_register_func init,
693+
int32_t module_api_version) {
630694
node::Environment* node_env = node::Environment::GetCurrent(context);
631695
std::string module_filename = "";
632696
if (init == nullptr) {
@@ -654,7 +718,7 @@ void napi_module_register_by_symbol(v8::Local<v8::Object> exports,
654718
}
655719

656720
// Create a new napi_env for this specific module.
657-
napi_env env = v8impl::NewEnv(context, module_filename);
721+
napi_env env = v8impl::NewEnv(context, module_filename, module_api_version);
658722

659723
napi_value _exports = nullptr;
660724
env->CallIntoModule([&](napi_env env) {

0 commit comments

Comments
 (0)