Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit e2553b1

Browse filesBrowse files
vmorozMoLow
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 9186f3a commit e2553b1
Copy full SHA for e2553b1

File tree

Expand file treeCollapse file tree

17 files changed

+620
-73
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

17 files changed

+620
-73
lines changed
Open diff view settings
Collapse file

‎doc/api/n-api.md‎

Copy file name to clipboardExpand all lines: doc/api/n-api.md
+38-23Lines changed: 38 additions & 23 deletions
  • Display the source diff
  • Display the rich diff
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
Collapse file

‎src/api/environment.cc‎

Copy file name to clipboardExpand all lines: src/api/environment.cc
+11-19Lines 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
}
Collapse file

‎src/js_native_api_v8.cc‎

Copy file name to clipboardExpand all lines: src/js_native_api_v8.cc
+25-6Lines 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(
Collapse file

‎src/js_native_api_v8.h‎

Copy file name to clipboardExpand all lines: src/js_native_api_v8.h
+7-2Lines 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
Collapse file

‎src/node.h‎

Copy file name to clipboardExpand all lines: src/node.h
+5-3Lines 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.
Collapse file

‎src/node_api.cc‎

Copy file name to clipboardExpand all lines: src/node_api.cc
+70-6Lines 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 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.