Skip to content

Commit e2553b1

Browse files
vmorozMoLow
authored andcommittedJul 6, 2023
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

17 files changed

+620
-73
lines changed
 

‎doc/api/n-api.md

+38-23
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

+11-19
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

+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
@@ -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

+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) {

‎src/node_api.h

+10
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ struct uv_loop_s; // Forward declaration.
3030

3131
typedef napi_value(NAPI_CDECL* napi_addon_register_func)(napi_env env,
3232
napi_value exports);
33+
typedef int32_t(NAPI_CDECL* node_api_addon_get_api_version_func)();
3334

3435
// Used by deprecated registration method napi_module_register.
3536
typedef struct napi_module {
@@ -54,11 +55,20 @@ typedef struct napi_module {
5455
#define NAPI_MODULE_INITIALIZER_BASE napi_register_module_v
5556
#endif
5657

58+
#define NODE_API_MODULE_GET_API_VERSION_BASE node_api_module_get_api_version_v
59+
5760
#define NAPI_MODULE_INITIALIZER \
5861
NAPI_MODULE_INITIALIZER_X(NAPI_MODULE_INITIALIZER_BASE, NAPI_MODULE_VERSION)
5962

63+
#define NODE_API_MODULE_GET_API_VERSION \
64+
NAPI_MODULE_INITIALIZER_X(NODE_API_MODULE_GET_API_VERSION_BASE, \
65+
NAPI_MODULE_VERSION)
66+
6067
#define NAPI_MODULE_INIT() \
6168
EXTERN_C_START \
69+
NAPI_MODULE_EXPORT int32_t NODE_API_MODULE_GET_API_VERSION() { \
70+
return NAPI_VERSION; \
71+
} \
6272
NAPI_MODULE_EXPORT napi_value NAPI_MODULE_INITIALIZER(napi_env env, \
6373
napi_value exports); \
6474
EXTERN_C_END \

‎src/node_api_internals.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010

1111
struct node_napi_env__ : public napi_env__ {
1212
node_napi_env__(v8::Local<v8::Context> context,
13-
const std::string& module_filename);
13+
const std::string& module_filename,
14+
int32_t module_api_version);
1415

1516
bool can_call_into_js() const override;
1617
void CallFinalizer(napi_finalize cb, void* data, void* hint) override;

‎src/node_binding.cc

+12-1
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,12 @@ inline napi_addon_register_func GetNapiInitializerCallback(DLib* dlib) {
410410
dlib->GetSymbolAddress(name));
411411
}
412412

413+
inline node_api_addon_get_api_version_func GetNapiAddonGetApiVersionCallback(
414+
DLib* dlib) {
415+
return reinterpret_cast<node_api_addon_get_api_version_func>(
416+
dlib->GetSymbolAddress(STRINGIFY(NODE_API_MODULE_GET_API_VERSION)));
417+
}
418+
413419
// DLOpen is process.dlopen(module, filename, flags).
414420
// Used to load 'module.node' dynamically shared objects.
415421
//
@@ -486,7 +492,12 @@ void DLOpen(const FunctionCallbackInfo<Value>& args) {
486492
callback(exports, module, context);
487493
return true;
488494
} else if (auto napi_callback = GetNapiInitializerCallback(dlib)) {
489-
napi_module_register_by_symbol(exports, module, context, napi_callback);
495+
int32_t module_api_version = NODE_API_DEFAULT_MODULE_API_VERSION;
496+
if (auto get_version = GetNapiAddonGetApiVersionCallback(dlib)) {
497+
module_api_version = get_version();
498+
}
499+
napi_module_register_by_symbol(
500+
exports, module, context, napi_callback, module_api_version);
490501
return true;
491502
} else {
492503
mp = dlib->GetSavedModuleFromGlobalHandleMap();

‎src/node_binding.h

+12-5
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ enum {
2121

2222
// Make sure our internal values match the public API's values.
2323
static_assert(static_cast<int>(NM_F_LINKED) ==
24-
static_cast<int>(node::ModuleFlags::kLinked),
24+
static_cast<int>(node::ModuleFlags::kLinked),
2525
"NM_F_LINKED != node::ModuleFlags::kLinked");
2626

2727
#define NODE_BINDING_CONTEXT_AWARE_CPP(modname, regfunc, priv, flags) \
@@ -37,10 +37,17 @@ static_assert(static_cast<int>(NM_F_LINKED) ==
3737
nullptr}; \
3838
void _register_##modname() { node_module_register(&_module); }
3939

40-
void napi_module_register_by_symbol(v8::Local<v8::Object> exports,
41-
v8::Local<v8::Value> module,
42-
v8::Local<v8::Context> context,
43-
napi_addon_register_func init);
40+
void napi_module_register_by_symbol(
41+
v8::Local<v8::Object> exports,
42+
v8::Local<v8::Value> module,
43+
v8::Local<v8::Context> context,
44+
napi_addon_register_func init,
45+
int32_t module_api_version = NODE_API_DEFAULT_MODULE_API_VERSION);
46+
47+
node::addon_context_register_func get_node_api_context_register_func(
48+
node::Environment* node_env,
49+
const char* module_name,
50+
int32_t module_api_version);
4451

4552
namespace node {
4653

‎src/node_version.h

+5-1
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,10 @@
9393

9494
// The NAPI_VERSION provided by this version of the runtime. This is the version
9595
// which the Node binary being built supports.
96-
#define NAPI_VERSION 8
96+
#define NAPI_VERSION 8
97+
98+
// Node API modules use NAPI_VERSION 8 by default if it is not explicitly
99+
// specified. It must be always 8.
100+
#define NODE_API_DEFAULT_MODULE_API_VERSION 8
97101

98102
#endif // SRC_NODE_VERSION_H_

‎test/cctest/test_linked_binding.cc

+89-5
Original file line numberDiff line numberDiff line change
@@ -132,11 +132,15 @@ TEST_F(LinkedBindingTest, LocallyDefinedLinkedBindingNapiCallbackTest) {
132132
const Argv argv;
133133
Env test_env{handle_scope, argv};
134134

135-
AddLinkedBinding(*test_env, "local_linked_napi", InitializeLocalNapiBinding);
135+
AddLinkedBinding(*test_env,
136+
"local_linked_napi_cb",
137+
InitializeLocalNapiBinding,
138+
NAPI_VERSION);
136139

137140
v8::Local<v8::Context> context = isolate_->GetCurrentContext();
138141

139-
const char* run_script = "process._linkedBinding('local_linked_napi').hello";
142+
const char* run_script =
143+
"process._linkedBinding('local_linked_napi_cb').hello";
140144
v8::Local<v8::Script> script =
141145
v8::Script::Compile(
142146
context,
@@ -150,6 +154,84 @@ TEST_F(LinkedBindingTest, LocallyDefinedLinkedBindingNapiCallbackTest) {
150154
CHECK_EQ(strcmp(*utf8val, "world"), 0);
151155
}
152156

157+
static int32_t node_api_version = NODE_API_DEFAULT_MODULE_API_VERSION;
158+
159+
napi_value InitializeLocalNapiRefBinding(napi_env env, napi_value exports) {
160+
napi_value key, value;
161+
CHECK_EQ(
162+
napi_create_string_utf8(env, "napi_ref_created", NAPI_AUTO_LENGTH, &key),
163+
napi_ok);
164+
165+
// In experimental Node-API version we can create napi_ref to any value type.
166+
// Here we are trying to create a reference to the key string.
167+
napi_ref ref{};
168+
if (node_api_version == NAPI_VERSION_EXPERIMENTAL) {
169+
CHECK_EQ(napi_create_reference(env, key, 1, &ref), napi_ok);
170+
CHECK_EQ(napi_delete_reference(env, ref), napi_ok);
171+
} else {
172+
CHECK_EQ(napi_create_reference(env, key, 1, &ref), napi_invalid_arg);
173+
}
174+
CHECK_EQ(napi_get_boolean(env, ref != nullptr, &value), napi_ok);
175+
CHECK_EQ(napi_set_property(env, exports, key, value), napi_ok);
176+
return nullptr;
177+
}
178+
179+
// napi_ref in Node-API version 8 cannot accept strings.
180+
TEST_F(LinkedBindingTest, LocallyDefinedLinkedBindingNapiRefVersion8Test) {
181+
node_api_version = NODE_API_DEFAULT_MODULE_API_VERSION;
182+
const v8::HandleScope handle_scope(isolate_);
183+
const Argv argv;
184+
Env test_env{handle_scope, argv};
185+
186+
AddLinkedBinding(*test_env,
187+
"local_linked_napi_ref_v8",
188+
InitializeLocalNapiRefBinding,
189+
node_api_version);
190+
191+
v8::Local<v8::Context> context = isolate_->GetCurrentContext();
192+
193+
const char* run_script =
194+
"process._linkedBinding('local_linked_napi_ref_v8').napi_ref_created";
195+
v8::Local<v8::Script> script =
196+
v8::Script::Compile(
197+
context,
198+
v8::String::NewFromOneByte(
199+
isolate_, reinterpret_cast<const uint8_t*>(run_script))
200+
.ToLocalChecked())
201+
.ToLocalChecked();
202+
v8::Local<v8::Value> completion_value = script->Run(context).ToLocalChecked();
203+
CHECK(completion_value->IsBoolean());
204+
CHECK(!completion_value.As<v8::Boolean>()->Value());
205+
}
206+
207+
// Experimental version of napi_ref in Node-API can accept strings.
208+
TEST_F(LinkedBindingTest, LocallyDefinedLinkedBindingNapiRefExperimentalTest) {
209+
node_api_version = NAPI_VERSION_EXPERIMENTAL;
210+
const v8::HandleScope handle_scope(isolate_);
211+
const Argv argv;
212+
Env test_env{handle_scope, argv};
213+
214+
AddLinkedBinding(*test_env,
215+
"local_linked_napi_ref_experimental",
216+
InitializeLocalNapiRefBinding,
217+
node_api_version);
218+
219+
v8::Local<v8::Context> context = isolate_->GetCurrentContext();
220+
221+
const char* run_script = "process._linkedBinding('local_linked_napi_ref_"
222+
"experimental').napi_ref_created";
223+
v8::Local<v8::Script> script =
224+
v8::Script::Compile(
225+
context,
226+
v8::String::NewFromOneByte(
227+
isolate_, reinterpret_cast<const uint8_t*>(run_script))
228+
.ToLocalChecked())
229+
.ToLocalChecked();
230+
v8::Local<v8::Value> completion_value = script->Run(context).ToLocalChecked();
231+
CHECK(completion_value->IsBoolean());
232+
CHECK(completion_value.As<v8::Boolean>()->Value());
233+
}
234+
153235
napi_value NapiLinkedWithInstanceData(napi_env env, napi_value exports) {
154236
int* instance_data = new int(0);
155237
CHECK_EQ(napi_set_instance_data(
@@ -223,13 +305,15 @@ TEST_F(LinkedBindingTest,
223305
const Argv argv;
224306
Env test_env{handle_scope, argv};
225307

226-
AddLinkedBinding(
227-
*test_env, "local_linked_napi_id", NapiLinkedWithInstanceData);
308+
AddLinkedBinding(*test_env,
309+
"local_linked_napi_id_cb",
310+
NapiLinkedWithInstanceData,
311+
NAPI_VERSION);
228312

229313
v8::Local<v8::Context> context = isolate_->GetCurrentContext();
230314

231315
const char* run_script =
232-
"process._linkedBinding('local_linked_napi_id').hello";
316+
"process._linkedBinding('local_linked_napi_id_cb').hello";
233317
v8::Local<v8::Script> script =
234318
v8::Script::Compile(
235319
context,

‎test/cctest/test_node_api.cc

+2-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ TEST_F(NodeApiTest, CreateNodeApiEnv) {
3434
};
3535
Local<Object> module_obj = Object::New(isolate_);
3636
Local<Object> exports_obj = Object::New(isolate_);
37-
napi_module_register_by_symbol(exports_obj, module_obj, env->context(), init);
37+
napi_module_register_by_symbol(
38+
exports_obj, module_obj, env->context(), init, NAPI_VERSION);
3839
ASSERT_NE(addon_env, nullptr);
3940
node_napi_env internal_env = reinterpret_cast<node_napi_env>(addon_env);
4041
EXPECT_EQ(internal_env->node_env(), env);

‎test/js-native-api/test_reference/test.js

+14
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,27 @@ async function runTests() {
2525
const symbol = test_reference.createSymbolFor('testSymFor');
2626
test_reference.createReference(symbol, 0);
2727
assert.strictEqual(test_reference.referenceValue, symbol);
28+
})();
29+
test_reference.deleteReference();
30+
31+
(() => {
32+
const symbol = test_reference.createSymbolFor('testSymFor');
33+
test_reference.createReference(symbol, 1);
34+
assert.strictEqual(test_reference.referenceValue, symbol);
2835
assert.strictEqual(test_reference.referenceValue, Symbol.for('testSymFor'));
2936
})();
3037
test_reference.deleteReference();
3138

3239
(() => {
3340
const symbol = test_reference.createSymbolForEmptyString();
3441
test_reference.createReference(symbol, 0);
42+
assert.strictEqual(test_reference.referenceValue, Symbol.for(''));
43+
})();
44+
test_reference.deleteReference();
45+
46+
(() => {
47+
const symbol = test_reference.createSymbolForEmptyString();
48+
test_reference.createReference(symbol, 1);
3549
assert.strictEqual(test_reference.referenceValue, symbol);
3650
assert.strictEqual(test_reference.referenceValue, Symbol.for(''));
3751
})();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"targets": [
3+
{
4+
"target_name": "test_reference_all_types",
5+
"sources": [ "test_reference_by_node_api_version.c" ],
6+
"defines": [ "NAPI_EXPERIMENTAL" ],
7+
},
8+
{
9+
"target_name": "test_reference_obj_only",
10+
"sources": [ "test_reference_by_node_api_version.c" ],
11+
"defines": [ "NAPI_VERSION=8" ],
12+
}
13+
]
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
'use strict';
2+
// Flags: --expose-gc
3+
//
4+
// Testing API calls for Node-API references.
5+
// We compare their behavior between Node-API version 8 and later.
6+
// In version 8 references can be created only for object, function,
7+
// and symbol types, while in newer versions they can be created for
8+
// any value type.
9+
//
10+
const { gcUntil, buildType } = require('../../common');
11+
const assert = require('assert');
12+
const addon_v8 = require(`./build/${buildType}/test_reference_obj_only`);
13+
const addon_new = require(`./build/${buildType}/test_reference_all_types`);
14+
15+
async function runTests(addon, isVersion8, isLocalSymbol) {
16+
let allEntries = [];
17+
18+
(() => {
19+
// Create values of all napi_valuetype types.
20+
const undefinedValue = undefined;
21+
const nullValue = null;
22+
const booleanValue = false;
23+
const numberValue = 42;
24+
const stringValue = 'test_string';
25+
const globalSymbolValue = Symbol.for('test_symbol_global');
26+
const localSymbolValue = Symbol('test_symbol_local');
27+
const symbolValue = isLocalSymbol ? localSymbolValue : globalSymbolValue;
28+
const objectValue = { x: 1, y: 2 };
29+
const functionValue = (x, y) => x + y;
30+
const externalValue = addon.createExternal();
31+
const bigintValue = 9007199254740991n;
32+
33+
// The position of entries in the allEntries array corresponds to the
34+
// napi_valuetype enum value. See the CreateRef function for the
35+
// implementation details.
36+
allEntries = [
37+
{ value: undefinedValue, canBeWeak: false, canBeRefV8: false },
38+
{ value: nullValue, canBeWeak: false, canBeRefV8: false },
39+
{ value: booleanValue, canBeWeak: false, canBeRefV8: false },
40+
{ value: numberValue, canBeWeak: false, canBeRefV8: false },
41+
{ value: stringValue, canBeWeak: false, canBeRefV8: false },
42+
{ value: symbolValue, canBeWeak: isLocalSymbol, canBeRefV8: true,
43+
isAlwaysStrong: !isLocalSymbol },
44+
{ value: objectValue, canBeWeak: true, canBeRefV8: true },
45+
{ value: functionValue, canBeWeak: true, canBeRefV8: true },
46+
{ value: externalValue, canBeWeak: true, canBeRefV8: true },
47+
{ value: bigintValue, canBeWeak: false, canBeRefV8: false },
48+
];
49+
50+
// Go over all values of different types, create strong ref values for
51+
// them, read the stored values, and check how the ref count works.
52+
for (const entry of allEntries) {
53+
if (!isVersion8 || entry.canBeRefV8) {
54+
const index = addon.createRef(entry.value);
55+
const refValue = addon.getRefValue(index);
56+
assert.strictEqual(entry.value, refValue);
57+
assert.strictEqual(addon.ref(index), 2);
58+
assert.strictEqual(addon.unref(index), 1);
59+
assert.strictEqual(addon.unref(index), 0);
60+
} else {
61+
assert.throws(() => { addon.createRef(entry.value); },
62+
{
63+
name: 'Error',
64+
message: 'Invalid argument',
65+
});
66+
}
67+
}
68+
69+
// When the reference count is zero, then object types become weak pointers
70+
// and other types are released.
71+
// Here we know that the GC is not run yet because the values are
72+
// still in the allEntries array.
73+
allEntries.forEach((entry, index) => {
74+
if (!isVersion8 || entry.canBeRefV8) {
75+
if (entry.canBeWeak || entry.isAlwaysStrong) {
76+
assert.strictEqual(addon.getRefValue(index), entry.value);
77+
} else {
78+
assert.strictEqual(addon.getRefValue(index), undefined);
79+
}
80+
}
81+
// Set to undefined to allow GC collect the value.
82+
entry.value = undefined;
83+
});
84+
85+
// To check that GC pass is done.
86+
const objWithFinalizer = {};
87+
addon.addFinalizer(objWithFinalizer);
88+
})();
89+
90+
addon.initFinalizeCount();
91+
assert.strictEqual(addon.getFinalizeCount(), 0);
92+
await gcUntil('Wait until a finalizer is called',
93+
() => (addon.getFinalizeCount() === 1));
94+
95+
// Create and call finalizer again to make sure that we had another GC pass.
96+
(() => {
97+
const objWithFinalizer = {};
98+
addon.addFinalizer(objWithFinalizer);
99+
})();
100+
await gcUntil('Wait until a finalizer is called again',
101+
() => (addon.getFinalizeCount() === 2));
102+
103+
// After GC and finalizers run, all values that support weak reference
104+
// semantic must return undefined value.
105+
allEntries.forEach((entry, index) => {
106+
if (!isVersion8 || entry.canBeRefV8) {
107+
if (!entry.isAlwaysStrong) {
108+
assert.strictEqual(addon.getRefValue(index), undefined);
109+
} else {
110+
assert.notStrictEqual(addon.getRefValue(index), undefined);
111+
}
112+
addon.deleteRef(index);
113+
}
114+
});
115+
}
116+
117+
async function runAllTests() {
118+
await runTests(addon_v8, /* isVersion8 */ true, /* isLocalSymbol */ true);
119+
await runTests(addon_v8, /* isVersion8 */ true, /* isLocalSymbol */ false);
120+
await runTests(addon_new, /* isVersion8 */ false, /* isLocalSymbol */ true);
121+
await runTests(addon_new, /* isVersion8 */ false, /* isLocalSymbol */ false);
122+
}
123+
124+
runAllTests();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
#include <node_api.h>
2+
#include "../../js-native-api/common.h"
3+
#include "stdlib.h"
4+
5+
#define NODE_API_ASSERT_STATUS(env, assertion, message) \
6+
NODE_API_ASSERT_BASE(env, assertion, message, napi_generic_failure)
7+
8+
#define NODE_API_CHECK_STATUS(env, the_call) \
9+
do { \
10+
napi_status status = (the_call); \
11+
if (status != napi_ok) { \
12+
return status; \
13+
} \
14+
} while (0)
15+
16+
static uint32_t finalizeCount = 0;
17+
18+
static void FreeData(napi_env env, void* data, void* hint) {
19+
NODE_API_ASSERT_RETURN_VOID(env, data != NULL, "Expects non-NULL data.");
20+
free(data);
21+
}
22+
23+
static void Finalize(napi_env env, void* data, void* hint) {
24+
++finalizeCount;
25+
}
26+
27+
static napi_status GetArgValue(napi_env env,
28+
napi_callback_info info,
29+
napi_value* argValue) {
30+
size_t argc = 1;
31+
NODE_API_CHECK_STATUS(
32+
env, napi_get_cb_info(env, info, &argc, argValue, NULL, NULL));
33+
34+
NODE_API_ASSERT_STATUS(env, argc == 1, "Expects one arg.");
35+
return napi_ok;
36+
}
37+
38+
static napi_status GetArgValueAsIndex(napi_env env,
39+
napi_callback_info info,
40+
uint32_t* index) {
41+
napi_value argValue;
42+
NODE_API_CHECK_STATUS(env, GetArgValue(env, info, &argValue));
43+
44+
napi_valuetype valueType;
45+
NODE_API_CHECK_STATUS(env, napi_typeof(env, argValue, &valueType));
46+
NODE_API_ASSERT_STATUS(
47+
env, valueType == napi_number, "Argument must be a number.");
48+
49+
return napi_get_value_uint32(env, argValue, index);
50+
}
51+
52+
static napi_status GetRef(napi_env env,
53+
napi_callback_info info,
54+
napi_ref* ref) {
55+
uint32_t index;
56+
NODE_API_CHECK_STATUS(env, GetArgValueAsIndex(env, info, &index));
57+
58+
napi_ref* refValues;
59+
NODE_API_CHECK_STATUS(env, napi_get_instance_data(env, (void**)&refValues));
60+
NODE_API_ASSERT_STATUS(env, refValues != NULL, "Cannot get instance data.");
61+
62+
*ref = refValues[index];
63+
return napi_ok;
64+
}
65+
66+
static napi_value ToUInt32Value(napi_env env, uint32_t value) {
67+
napi_value result;
68+
NODE_API_CALL(env, napi_create_uint32(env, value, &result));
69+
return result;
70+
}
71+
72+
static napi_status InitRefArray(napi_env env) {
73+
// valueRefs array has one entry per napi_valuetype
74+
napi_ref* valueRefs = malloc(sizeof(napi_ref) * ((int)napi_bigint + 1));
75+
return napi_set_instance_data(env, valueRefs, &FreeData, NULL);
76+
}
77+
78+
static napi_value CreateExternal(napi_env env, napi_callback_info info) {
79+
napi_value result;
80+
int* data = (int*)malloc(sizeof(int));
81+
*data = 42;
82+
NODE_API_CALL(env, napi_create_external(env, data, &FreeData, NULL, &result));
83+
return result;
84+
}
85+
86+
static napi_value CreateRef(napi_env env, napi_callback_info info) {
87+
napi_value argValue;
88+
NODE_API_CALL(env, GetArgValue(env, info, &argValue));
89+
90+
napi_valuetype valueType;
91+
NODE_API_CALL(env, napi_typeof(env, argValue, &valueType));
92+
uint32_t index = (uint32_t)valueType;
93+
94+
napi_ref* valueRefs;
95+
NODE_API_CALL(env, napi_get_instance_data(env, (void**)&valueRefs));
96+
NODE_API_CALL(env,
97+
napi_create_reference(env, argValue, 1, valueRefs + index));
98+
99+
return ToUInt32Value(env, index);
100+
}
101+
102+
static napi_value GetRefValue(napi_env env, napi_callback_info info) {
103+
napi_ref refValue;
104+
NODE_API_CALL(env, GetRef(env, info, &refValue));
105+
napi_value value;
106+
NODE_API_CALL(env, napi_get_reference_value(env, refValue, &value));
107+
return value;
108+
}
109+
110+
static napi_value Ref(napi_env env, napi_callback_info info) {
111+
napi_ref refValue;
112+
NODE_API_CALL(env, GetRef(env, info, &refValue));
113+
uint32_t refCount;
114+
NODE_API_CALL(env, napi_reference_ref(env, refValue, &refCount));
115+
return ToUInt32Value(env, refCount);
116+
}
117+
118+
static napi_value Unref(napi_env env, napi_callback_info info) {
119+
napi_ref refValue;
120+
NODE_API_CALL(env, GetRef(env, info, &refValue));
121+
uint32_t refCount;
122+
NODE_API_CALL(env, napi_reference_unref(env, refValue, &refCount));
123+
return ToUInt32Value(env, refCount);
124+
}
125+
126+
static napi_value DeleteRef(napi_env env, napi_callback_info info) {
127+
napi_ref refValue;
128+
NODE_API_CALL(env, GetRef(env, info, &refValue));
129+
NODE_API_CALL(env, napi_delete_reference(env, refValue));
130+
return NULL;
131+
}
132+
133+
static napi_value AddFinalizer(napi_env env, napi_callback_info info) {
134+
napi_value obj;
135+
NODE_API_CALL(env, GetArgValue(env, info, &obj));
136+
137+
napi_valuetype valueType;
138+
NODE_API_CALL(env, napi_typeof(env, obj, &valueType));
139+
NODE_API_ASSERT(env, valueType == napi_object, "Argument must be an object.");
140+
141+
NODE_API_CALL(env, napi_add_finalizer(env, obj, NULL, &Finalize, NULL, NULL));
142+
return NULL;
143+
}
144+
145+
static napi_value GetFinalizeCount(napi_env env, napi_callback_info info) {
146+
return ToUInt32Value(env, finalizeCount);
147+
}
148+
149+
static napi_value InitFinalizeCount(napi_env env, napi_callback_info info) {
150+
finalizeCount = 0;
151+
return NULL;
152+
}
153+
154+
EXTERN_C_START
155+
156+
NAPI_MODULE_INIT() {
157+
finalizeCount = 0;
158+
NODE_API_CALL(env, InitRefArray(env));
159+
160+
napi_property_descriptor properties[] = {
161+
DECLARE_NODE_API_PROPERTY("createExternal", CreateExternal),
162+
DECLARE_NODE_API_PROPERTY("createRef", CreateRef),
163+
DECLARE_NODE_API_PROPERTY("getRefValue", GetRefValue),
164+
DECLARE_NODE_API_PROPERTY("ref", Ref),
165+
DECLARE_NODE_API_PROPERTY("unref", Unref),
166+
DECLARE_NODE_API_PROPERTY("deleteRef", DeleteRef),
167+
DECLARE_NODE_API_PROPERTY("addFinalizer", AddFinalizer),
168+
DECLARE_NODE_API_PROPERTY("getFinalizeCount", GetFinalizeCount),
169+
DECLARE_NODE_API_PROPERTY("initFinalizeCount", InitFinalizeCount),
170+
};
171+
172+
NODE_API_CALL(
173+
env,
174+
napi_define_properties(
175+
env, exports, sizeof(properties) / sizeof(*properties), properties));
176+
177+
return exports;
178+
}
179+
180+
EXTERN_C_END

0 commit comments

Comments
 (0)
Please sign in to comment.