Skip to content

Commit e2553b1

Browse files
vmorozMoLow
authored andcommitted
node-api: get Node API version used by addon
PR-URL: #45715 Reviewed-By: Gabriel Schulhof <[email protected]> Reviewed-By: Michael Dawson <[email protected]> Reviewed-By: Chengzhong Wu <[email protected]>
1 parent 9186f3a commit e2553b1

File tree

17 files changed

+620
-73
lines changed

17 files changed

+620
-73
lines changed

doc/api/n-api.md

Lines changed: 38 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1637,25 +1637,36 @@ If it is called more than once an error will be returned.
16371637

16381638
This API can be called even if there is a pending JavaScript exception.
16391639

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

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

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

16601671
References can be created with an initial reference count. The count can
16611672
then be modified through [`napi_reference_ref`][] and
@@ -1666,6 +1677,11 @@ will return `NULL` for the returned `napi_value`. An attempt to call
16661677
[`napi_reference_ref`][] for a reference whose object has been collected
16671678
results in an error.
16681679

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

17001716
* `[in] env`: The environment that the API is invoked under.
1701-
* `[in] value`: `napi_value` representing the `Object` to which we want a
1702-
reference.
1717+
* `[in] value`: The `napi_value` for which a reference is being created.
17031718
* `[in] initial_refcount`: Initial reference count for the new reference.
17041719
* `[out] result`: `napi_ref` pointing to the new reference.
17051720

17061721
Returns `napi_ok` if the API succeeded.
17071722

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

17111730
#### `napi_delete_reference`
17121731

@@ -1785,18 +1804,15 @@ NAPI_EXTERN napi_status napi_get_reference_value(napi_env env,
17851804
napi_value* result);
17861805
```
17871806

1788-
the `napi_value passed` in or out of these methods is a handle to the
1789-
object to which the reference is related.
1790-
17911807
* `[in] env`: The environment that the API is invoked under.
1792-
* `[in] ref`: `napi_ref` for which we requesting the corresponding `Object`.
1793-
* `[out] result`: The `napi_value` for the `Object` referenced by the
1794-
`napi_ref`.
1808+
* `[in] ref`: The `napi_ref` for which the corresponding value is
1809+
being requested.
1810+
* `[out] result`: The `napi_value` referenced by the `napi_ref`.
17951811

17961812
Returns `napi_ok` if the API succeeded.
17971813

17981814
If still valid, this API returns the `napi_value` representing the
1799-
JavaScript `Object` associated with the `napi_ref`. Otherwise, result
1815+
JavaScript value associated with the `napi_ref`. Otherwise, result
18001816
will be `NULL`.
18011817

18021818
### Cleanup on exit of the current Node.js environment
@@ -5065,9 +5081,8 @@ napi_status napi_define_class(napi_env env,
50655081
```
50665082

50675083
* `[in] env`: The environment that the API is invoked under.
5068-
* `[in] utf8name`: Name of the JavaScript constructor function; When wrapping a
5069-
C++ class, we recommend for clarity that this name be the same as that of
5070-
the C++ class.
5084+
* `[in] utf8name`: Name of the JavaScript constructor function. For clarity,
5085+
it is recommended to use the C++ class name when wrapping a C++ class.
50715086
* `[in] length`: The length of the `utf8name` in bytes, or `NAPI_AUTO_LENGTH`
50725087
if it is null-terminated.
50735088
* `[in] constructor`: Callback function that handles constructing instances

src/api/environment.cc

Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -808,26 +808,18 @@ void AddLinkedBinding(Environment* env,
808808

809809
void AddLinkedBinding(Environment* env,
810810
const char* name,
811-
napi_addon_register_func fn) {
811+
napi_addon_register_func fn,
812+
int32_t module_api_version) {
812813
node_module mod = {
813-
-1,
814-
NM_F_LINKED,
815-
nullptr, // nm_dso_handle
816-
nullptr, // nm_filename
817-
nullptr, // nm_register_func
818-
[](v8::Local<v8::Object> exports,
819-
v8::Local<v8::Value> module,
820-
v8::Local<v8::Context> context,
821-
void* priv) {
822-
napi_module_register_by_symbol(
823-
exports,
824-
module,
825-
context,
826-
reinterpret_cast<napi_addon_register_func>(priv));
827-
},
828-
name,
829-
reinterpret_cast<void*>(fn),
830-
nullptr // nm_link
814+
-1, // nm_version for Node-API
815+
NM_F_LINKED, // nm_flags
816+
nullptr, // nm_dso_handle
817+
nullptr, // nm_filename
818+
nullptr, // nm_register_func
819+
get_node_api_context_register_func(env, name, module_api_version),
820+
name, // nm_modname
821+
reinterpret_cast<void*>(fn), // nm_priv
822+
nullptr // nm_link
831823
};
832824
AddLinkedBinding(env, mod);
833825
}

src/js_native_api_v8.cc

Lines changed: 25 additions & 6 deletions
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

Lines changed: 7 additions & 2 deletions
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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1098,9 +1098,11 @@ NODE_EXTERN void AddLinkedBinding(Environment* env,
10981098
const char* name,
10991099
addon_context_register_func fn,
11001100
void* priv);
1101-
NODE_EXTERN void AddLinkedBinding(Environment* env,
1102-
const char* name,
1103-
napi_addon_register_func fn);
1101+
NODE_EXTERN void AddLinkedBinding(
1102+
Environment* env,
1103+
const char* name,
1104+
napi_addon_register_func fn,
1105+
int32_t module_api_version = NODE_API_DEFAULT_MODULE_API_VERSION);
11041106

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

src/node_api.cc

Lines changed: 70 additions & 6 deletions
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)