From 579d44cfbe994499f72308c8893829cea5b9b720 Mon Sep 17 00:00:00 2001 From: Vladimir Morozov Date: Mon, 10 Oct 2022 11:58:52 -0700 Subject: [PATCH 1/5] node-api: enable napi_ref for all value types --- doc/api/n-api.md | 68 +++++++ src/js_native_api.h | 5 + src/js_native_api_types.h | 31 +++ src/js_native_api_v8.cc | 32 ++- src/js_native_api_v8.h | 22 ++- src/node_api.cc | 34 +++- src/node_api.h | 34 +++- src/node_api_internals.h | 3 +- test/js-native-api/common.h | 1 + test/js-native-api/entry_point.c | 1 + test/js-native-api/test_reference/binding.gyp | 3 +- .../test_reference/test_reference.c | 3 + .../test_reference_all_types/binding.gyp | 11 ++ .../test_reference_all_types/test.js | 86 ++++++++ .../test_reference_all_types.c | 183 ++++++++++++++++++ 15 files changed, 497 insertions(+), 20 deletions(-) create mode 100644 test/js-native-api/test_reference_all_types/binding.gyp create mode 100644 test/js-native-api/test_reference_all_types/test.js create mode 100644 test/js-native-api/test_reference_all_types/test_reference_all_types.c diff --git a/doc/api/n-api.md b/doc/api/n-api.md index 3a2b9bceb2ab64..8524371bf7eaa8 100644 --- a/doc/api/n-api.md +++ b/doc/api/n-api.md @@ -2046,6 +2046,48 @@ the `napi_value` in question is of the JavaScript type expected by the API. ### Enum types +#### `napi_features` + + + +> Stability: 1 - Experimental + +```c +typedef enum { + napi_feature_none = 0, + napi_feature_reference_all_types = 1 << 0, + + napi_default_experimental_features = napi_feature_reference_all_types, + + napi_default_features = napi_default_experimental_features, // version specific +} napi_features; +``` + +The `napi_features` allow changing internal behavior of existing Node-API +functions. + +We pass a `napi_features` pointer to the `napi_module` struct in the +`NAPI_MODULE_X` macro. This macro is used for the module registration. +If the module is initialized without using this macro, then there will be +no features selected and the module will use the `napi_feature_none`. + +Each Node-API version defines its own default set of features. +For the current version it can be accessed using `napi_default_features`. +A module can override the set of its enabled features by adding +`NAPI_CUSTOM_FEATURES` definition to the `.gyp` file and then defining the +value of the global `napi_module_features` variable. +To check enabled features use the `napi_is_feature_enabled` function. + +For example, to disables `napi_feature_reference_all_types` feature we can +exclude its bit from the `napi_default_features` set: + +```c +napi_features napi_module_features = + napi_default_features & ~napi_feature_reference_all_types; +``` + #### `napi_key_collection_mode` + +> Stability: 1 - Experimental + +```c +NAPI_EXTERN napi_status napi_is_feature_enabled(napi_env env, + napi_features feature, + bool* result); +``` + +* `[in] env`: The environment that the API is invoked under. +* `[in] feature`: The feature that we want to test. +* `[out] result`: Whether the feature or a set of features are enabled. + +Returns `napi_ok` if the API succeeded. + +The function checks enabled features for the module. +If `feature` parameter has multiple `napi_features` bit flags, then the +function returns `true` only when all the requested fatures are enabled. + +See [`napi_features`][] for more details about Node-API features. + ## Memory management ### `napi_adjust_external_memory` diff --git a/src/js_native_api.h b/src/js_native_api.h index 220d140d4bfe9a..1bfb77ca262c75 100644 --- a/src/js_native_api.h +++ b/src/js_native_api.h @@ -566,6 +566,11 @@ NAPI_EXTERN napi_status NAPI_CDECL napi_object_seal(napi_env env, napi_value object); #endif // NAPI_VERSION >= 8 +#ifdef NAPI_EXPERIMENTAL +NAPI_EXTERN napi_status NAPI_CDECL +napi_is_feature_enabled(napi_env env, napi_features feature, bool* result); +#endif // NAPI_EXPERIMENTAL + EXTERN_C_END #endif // SRC_JS_NATIVE_API_H_ diff --git a/src/js_native_api_types.h b/src/js_native_api_types.h index 376930ba4a3220..34a3720f2db416 100644 --- a/src/js_native_api_types.h +++ b/src/js_native_api_types.h @@ -108,6 +108,37 @@ typedef enum { // * the definition of `napi_status` in doc/api/n-api.md to reflect the newly // added value(s). +#ifdef NAPI_EXPERIMENTAL +// Features allow changing internal behavior of existing Node-API functions. +// +// We pass a napi_features pointer to the napi_module struct +// in the NAPI_MODULE_X macro. This macro is used for the module registration. +// If the module is initialized without using this macro, then there will be +// no features selected and the module will use the napi_feature_none. +// +// Each Node-API version defines its own default set of features. +// For the current version it can be accessed using napi_default_features. +// A module can override the set of its enabled features by adding +// NAPI_CUSTOM_FEATURES definition to the .gyp file and then defining the +// value of the global napi_module_features variable. +// To check enabled features use the `napi_is_feature_enabled` function. +// +// For example, to disables napi_feature_reference_all_types: +// napi_features napi_module_features = +// napi_default_features & ~napi_feature_reference_all_types; +typedef enum { + // To be used when no features needs to be set. + napi_feature_none = 0, + // Use napi_ref for all value types. + // Not only objects, functions, and symbols as before. + napi_feature_reference_all_types = 1 << 0, + // Each version of NAPI is going to have its own default set of features. + napi_default_experimental_features = napi_feature_reference_all_types, + // This variable must be conditionally set depending on the NAPI version. + napi_default_features = napi_default_experimental_features, +} napi_features; +#endif + typedef napi_value(NAPI_CDECL* napi_callback)(napi_env env, napi_callback_info info); typedef void(NAPI_CDECL* napi_finalize)(napi_env env, diff --git a/src/js_native_api_v8.cc b/src/js_native_api_v8.cc index 58567c5e44a9e7..b068345a07591c 100644 --- a/src/js_native_api_v8.cc +++ b/src/js_native_api_v8.cc @@ -576,7 +576,9 @@ Reference::Reference(napi_env env, v8::Local value, Args&&... args) : RefBase(env, std::forward(args)...), _persistent(env->isolate, value), _secondPassParameter(new SecondPassCallParameterRef(this)), - _secondPassScheduled(false) { + _secondPassScheduled(false), + _canBeWeak(!env->IsFeatureEnabled(napi_feature_reference_all_types) || + value->IsObject() || value->IsFunction()) { if (RefCount() == 0) { SetWeak(); } @@ -652,7 +654,7 @@ void Reference::Finalize(bool is_env_teardown) { // the secondPassParameter so that even if it has been // scheduled no Finalization will be run. void Reference::ClearWeak() { - if (!_persistent.IsEmpty()) { + if (!_persistent.IsEmpty() && _canBeWeak) { _persistent.ClearWeak(); } if (_secondPassParameter != nullptr) { @@ -669,8 +671,13 @@ void Reference::SetWeak() { // nothing return; } - _persistent.SetWeak( - _secondPassParameter, FinalizeCallback, v8::WeakCallbackType::kParameter); + if (_canBeWeak) { + _persistent.SetWeak(_secondPassParameter, + FinalizeCallback, + v8::WeakCallbackType::kParameter); + } else { + _persistent.Reset(); + } *_secondPassParameter = this; } @@ -2495,9 +2502,11 @@ napi_status NAPI_CDECL napi_create_reference(napi_env env, CHECK_ARG(env, result); v8::Local v8_value = v8impl::V8LocalValueFromJsValue(value); - if (!(v8_value->IsObject() || v8_value->IsFunction() || - v8_value->IsSymbol())) { - return napi_set_last_error(env, napi_invalid_arg); + if (!env->IsFeatureEnabled(napi_feature_reference_all_types)) { + if (!(v8_value->IsObject() || v8_value->IsFunction() || + v8_value->IsSymbol())) { + return napi_set_last_error(env, napi_invalid_arg); + } } v8impl::Reference* reference = @@ -3257,3 +3266,12 @@ napi_status NAPI_CDECL napi_is_detached_arraybuffer(napi_env env, return napi_clear_last_error(env); } + +napi_status NAPI_CDECL napi_is_feature_enabled(napi_env env, + napi_features feature, + bool* result) { + CHECK_ENV(env); + CHECK_ARG(env, result); + *result = env->IsFeatureEnabled(feature); + return napi_clear_last_error(env); +} diff --git a/src/js_native_api_v8.h b/src/js_native_api_v8.h index 766398744c5dfb..2aba514ebdb4f2 100644 --- a/src/js_native_api_v8.h +++ b/src/js_native_api_v8.h @@ -50,9 +50,10 @@ class RefTracker { } // end of namespace v8impl struct napi_env__ { - explicit napi_env__(v8::Local context) + explicit napi_env__(v8::Local context, napi_features* features) : isolate(context->GetIsolate()), context_persistent(isolate, context) { CHECK_EQ(isolate, context->GetIsolate()); + SetFeatures(features); napi_clear_last_error(this); } @@ -89,13 +90,28 @@ struct napi_env__ { } } - // This should be overridden to schedule the finalization to a properiate + // This should be overridden to schedule the finalization to appropriate // timing, like next tick of the event loop. virtual void CallFinalizer(napi_finalize cb, void* data, void* hint) { v8::HandleScope handle_scope(isolate); CallIntoModule([&](napi_env env) { cb(env, data, hint); }); } + bool IsFeatureEnabled(napi_features feature) { + // By comparing results of `&` operation to the feature parameter + // we allow to test for multiple feature flags. + return (_features & feature) == feature; + } + + void SetFeatures(napi_features* features) { + if (features == nullptr) { + _features = napi_feature_none; + } else { + const napi_features availableFeatures = napi_default_features; + _features = static_cast(availableFeatures & *features); + } + } + virtual void DeleteMe() { // First we must finalize those references that have `napi_finalizer` // callbacks. The reason is that addons might store other references which @@ -122,6 +138,7 @@ struct napi_env__ { int open_callback_scopes = 0; int refs = 1; void* instance_data = nullptr; + napi_features _features = napi_feature_none; protected: // Should not be deleted directly. Delete with `napi_env__::DeleteMe()` @@ -435,6 +452,7 @@ class Reference : public RefBase { v8impl::Persistent _persistent; SecondPassCallParameterRef* _secondPassParameter; bool _secondPassScheduled; + const bool _canBeWeak; FRIEND_TEST(JsNativeApiV8Test, Reference); }; diff --git a/src/node_api.cc b/src/node_api.cc index 48b94a7c12873c..523f7edeb16447 100644 --- a/src/node_api.cc +++ b/src/node_api.cc @@ -20,8 +20,9 @@ #include node_napi_env__::node_napi_env__(v8::Local context, - const std::string& module_filename) - : napi_env__(context), filename(module_filename) { + const std::string& module_filename, + napi_features* features) + : napi_env__(context, features), filename(module_filename) { CHECK_NOT_NULL(node_env()); } @@ -126,10 +127,11 @@ class BufferFinalizer : private Finalizer { }; inline napi_env NewEnv(v8::Local context, - const std::string& module_filename) { + const std::string& module_filename, + napi_features* features) { node_napi_env result; - result = new node_napi_env__(context, module_filename); + result = new node_napi_env__(context, module_filename, features); // TODO(addaleax): There was previously code that tried to delete the // napi_env when its v8::Context was garbage collected; // However, as long as N-API addons using this napi_env are in place, @@ -586,6 +588,13 @@ class AsyncContext { } // end of namespace v8impl +void napi_module_register_by_symbol_with_features( + v8::Local exports, + v8::Local module, + v8::Local context, + napi_addon_register_func init, + napi_features* features); + // Intercepts the Node-V8 module registration callback. Converts parameters // to NAPI equivalents and then calls the registration callback specified // by the NAPI module. @@ -593,17 +602,28 @@ static void napi_module_register_cb(v8::Local exports, v8::Local module, v8::Local context, void* priv) { - napi_module_register_by_symbol( + napi_module_register_by_symbol_with_features( exports, module, context, - static_cast(priv)->nm_register_func); + static_cast(priv)->nm_register_func, + static_cast(priv)->nm_features); } void napi_module_register_by_symbol(v8::Local exports, v8::Local module, v8::Local context, napi_addon_register_func init) { + napi_module_register_by_symbol_with_features( + exports, module, context, init, nullptr); +} + +void napi_module_register_by_symbol_with_features( + v8::Local exports, + v8::Local module, + v8::Local context, + napi_addon_register_func init, + napi_features* features) { node::Environment* node_env = node::Environment::GetCurrent(context); std::string module_filename = ""; if (init == nullptr) { @@ -631,7 +651,7 @@ void napi_module_register_by_symbol(v8::Local exports, } // Create a new napi_env for this specific module. - napi_env env = v8impl::NewEnv(context, module_filename); + napi_env env = v8impl::NewEnv(context, module_filename, features); napi_value _exports; env->CallIntoModule([&](napi_env env) { diff --git a/src/node_api.h b/src/node_api.h index 3dc17f31f68778..4b065702d968d6 100644 --- a/src/node_api.h +++ b/src/node_api.h @@ -38,7 +38,12 @@ typedef struct napi_module { napi_addon_register_func nm_register_func; const char* nm_modname; void* nm_priv; +#ifdef NAPI_EXPERIMENTAL + napi_features* nm_features; + void* reserved[3]; +#else void* reserved[4]; +#endif } napi_module; #define NAPI_MODULE_VERSION 1 @@ -73,8 +78,31 @@ typedef struct napi_module { static void fn(void) #endif +#ifdef NAPI_EXPERIMENTAL +#ifdef NAPI_CUSTOM_FEATURES + +// Define value of napi_module_features variable in your module when +// NAPI_CUSTOM_FEATURES is set in gyp file. +extern napi_features napi_module_features; +#define NAPI_DEFINE_DEFAULT_FEATURES + +#else // NAPI_CUSTOM_FEATURES + +#define NAPI_DEFINE_DEFAULT_FEATURES \ + static napi_features napi_module_features = napi_default_features; + +#endif // NAPI_CUSTOM_FEATURES + +#define NAPI_FEATURES_PTR /* NOLINT */ &napi_module_features, + +#else // NAPI_EXPERIMENTAL +#define NAPI_DEFINE_DEFAULT_FEATURES +#define NAPI_FEATURES_PTR +#endif // NAPI_EXPERIMENTAL + #define NAPI_MODULE_X(modname, regfunc, priv, flags) \ EXTERN_C_START \ + NAPI_DEFINE_DEFAULT_FEATURES \ static napi_module _module = { \ NAPI_MODULE_VERSION, \ flags, \ @@ -82,9 +110,11 @@ typedef struct napi_module { regfunc, \ #modname, \ priv, \ - {0}, \ + NAPI_FEATURES_PTR{0}, \ }; \ - NAPI_C_CTOR(_register_##modname) { napi_module_register(&_module); } \ + NAPI_C_CTOR(_register_##modname) { \ + napi_module_register(&_module); \ + } \ EXTERN_C_END #define NAPI_MODULE_INITIALIZER_X(base, version) \ diff --git a/src/node_api_internals.h b/src/node_api_internals.h index de5d9dc0804367..92115249cab49f 100644 --- a/src/node_api_internals.h +++ b/src/node_api_internals.h @@ -10,7 +10,8 @@ struct node_napi_env__ : public napi_env__ { node_napi_env__(v8::Local context, - const std::string& module_filename); + const std::string& module_filename, + napi_features* features); bool can_call_into_js() const override; v8::Maybe mark_arraybuffer_as_untransferable( diff --git a/test/js-native-api/common.h b/test/js-native-api/common.h index 46784059a1f70a..2690900b844c31 100644 --- a/test/js-native-api/common.h +++ b/test/js-native-api/common.h @@ -1,3 +1,4 @@ +#define NAPI_EXPERIMENTAL #include // Empty value so that macros here are able to return NULL or void diff --git a/test/js-native-api/entry_point.c b/test/js-native-api/entry_point.c index 6b7b50a38c9535..d39d9c8579df98 100644 --- a/test/js-native-api/entry_point.c +++ b/test/js-native-api/entry_point.c @@ -1,3 +1,4 @@ +#define NAPI_EXPERIMENTAL #include EXTERN_C_START diff --git a/test/js-native-api/test_reference/binding.gyp b/test/js-native-api/test_reference/binding.gyp index 518fd21c37c566..26fbfc0b901f64 100644 --- a/test/js-native-api/test_reference/binding.gyp +++ b/test/js-native-api/test_reference/binding.gyp @@ -5,7 +5,8 @@ "sources": [ "../entry_point.c", "test_reference.c" - ] + ], + 'defines': [ 'NAPI_CUSTOM_FEATURES' ] } ] } diff --git a/test/js-native-api/test_reference/test_reference.c b/test/js-native-api/test_reference/test_reference.c index e9f3ec7a919542..1f1333bb999cae 100644 --- a/test/js-native-api/test_reference/test_reference.c +++ b/test/js-native-api/test_reference/test_reference.c @@ -283,4 +283,7 @@ napi_value Init(napi_env env, napi_value exports) { return exports; } + +// Make sure that this test uses the old napi_ref behavior. +napi_features napi_module_features = napi_default_features & ~napi_feature_reference_all_types; EXTERN_C_END diff --git a/test/js-native-api/test_reference_all_types/binding.gyp b/test/js-native-api/test_reference_all_types/binding.gyp new file mode 100644 index 00000000000000..9dd0ce7c4f5185 --- /dev/null +++ b/test/js-native-api/test_reference_all_types/binding.gyp @@ -0,0 +1,11 @@ +{ + "targets": [ + { + "target_name": "test_reference_all_types", + "sources": [ + "../entry_point.c", + "test_reference_all_types.c" + ] + } + ] +} diff --git a/test/js-native-api/test_reference_all_types/test.js b/test/js-native-api/test_reference_all_types/test.js new file mode 100644 index 00000000000000..c4701211e94ba0 --- /dev/null +++ b/test/js-native-api/test_reference_all_types/test.js @@ -0,0 +1,86 @@ +'use strict'; +// Flags: --expose-gc + +const { gcUntil, buildType } = require('../../common'); +const assert = require('assert'); + +// Testing API calls for references to all value types. +const addon = require(`./build/${buildType}/test_reference_all_types`); + +async function runTests() { + let entryCount = 0; + + (() => { + // Create values of all napi_valuetype types. + const undefinedValue = undefined; + const nullValue = null; + const booleanValue = false; + const numberValue = 42; + const stringValue = 'test_string'; + const symbolValue = Symbol.for('test_symbol'); + const objectValue = { x: 1, y: 2 }; + const functionValue = (x, y) => x + y; + const externalValue = addon.createExternal(); + const bigintValue = 9007199254740991n; + + const allValues = [ + undefinedValue, + nullValue, + booleanValue, + numberValue, + stringValue, + symbolValue, + objectValue, + functionValue, + externalValue, + bigintValue, + ]; + entryCount = allValues.length; + const objectValueIndex = allValues.indexOf(objectValue); + const functionValueIndex = allValues.indexOf(functionValue); + + // Go over all values of different types, create strong ref values for + // them, read the stored values, and check how the ref count works. + for (const value of allValues) { + const index = addon.createRef(value); + const refValue = addon.getRefValue(index); + assert.strictEqual(value, refValue); + assert.strictEqual(addon.ref(index), 2); + assert.strictEqual(addon.unref(index), 1); + assert.strictEqual(addon.unref(index), 0); + } + + // The references become weak pointers when the ref count is 0. + // To be compatible with the JavaScript spec we expect these + // types to be objects and functions. + // Here we know that the GC is not run yet because the values are + // still in the allValues array. + assert.strictEqual(addon.getRefValue(objectValueIndex), objectValue); + assert.strictEqual(addon.getRefValue(functionValueIndex), functionValue); + + const objWithFinalizer = {}; + addon.addFinalizer(objWithFinalizer); + })(); + + assert.strictEqual(addon.getFinalizeCount(), 0); + await gcUntil('Wait until a finalizer is called', + () => (addon.getFinalizeCount() === 1)); + + // Create and call finalizer again to make sure that all finalizers are run. + (() => { + const objWithFinalizer = {}; + addon.addFinalizer(objWithFinalizer); + })(); + + await gcUntil('Wait until a finalizer is called again', + () => (addon.getFinalizeCount() === 1)); + + // After GC and finalizers run, all references with refCount==0 must return + // undefined value. + for (let i = 0; i < entryCount; ++i) { + const refValue = addon.getRefValue(i); + assert.strictEqual(refValue, undefined); + addon.deleteRef(i); + } +} +runTests(); diff --git a/test/js-native-api/test_reference_all_types/test_reference_all_types.c b/test/js-native-api/test_reference_all_types/test_reference_all_types.c new file mode 100644 index 00000000000000..daa655efa0401e --- /dev/null +++ b/test/js-native-api/test_reference_all_types/test_reference_all_types.c @@ -0,0 +1,183 @@ +#include "../common.h" +#include "stdlib.h" + +#define NODE_API_ASSERT_STATUS(env, assertion, message) \ + NODE_API_ASSERT_BASE(env, assertion, message, napi_generic_failure) + +#define NODE_API_CHECK_STATUS(env, the_call) \ + do { \ + napi_status status = (the_call); \ + if (status != napi_ok) { \ + return status; \ + } \ + } while (0) + +static uint32_t finalizeCount = 0; + +static void FreeData(napi_env env, void* data, void* hint) { + NODE_API_ASSERT_RETURN_VOID(env, data != NULL, "Expects non-NULL data."); + free(data); +} + +static void Finalize(napi_env env, void* data, void* hint) { + ++finalizeCount; +} + +static napi_status GetArgValue(napi_env env, + napi_callback_info info, + napi_value* argValue) { + size_t argc = 1; + NODE_API_CHECK_STATUS( + env, napi_get_cb_info(env, info, &argc, argValue, NULL, NULL)); + + NODE_API_ASSERT_STATUS(env, argc == 1, "Expects one arg."); + return napi_ok; +} + +static napi_status GetArgValueAsIndex(napi_env env, + napi_callback_info info, + uint32_t* index) { + napi_value argValue; + NODE_API_CHECK_STATUS(env, GetArgValue(env, info, &argValue)); + + napi_valuetype valueType; + NODE_API_CHECK_STATUS(env, napi_typeof(env, argValue, &valueType)); + NODE_API_ASSERT_STATUS( + env, valueType == napi_number, "Argument must be a number."); + + return napi_get_value_uint32(env, argValue, index); +} + +static napi_status GetRef(napi_env env, + napi_callback_info info, + napi_ref* ref) { + uint32_t index; + NODE_API_CHECK_STATUS(env, GetArgValueAsIndex(env, info, &index)); + + napi_ref* refValues; + NODE_API_CHECK_STATUS(env, napi_get_instance_data(env, (void**)&refValues)); + NODE_API_ASSERT_STATUS(env, refValues != NULL, "Cannot get instance data."); + + *ref = refValues[index]; + return napi_ok; +} + +static napi_value ToUInt32Value(napi_env env, uint32_t value) { + napi_value result; + NODE_API_CALL(env, napi_create_uint32(env, value, &result)); + return result; +} + +static napi_status CheckFeature(napi_env env) { + bool canReferenceAllTypes; + NODE_API_CHECK_STATUS( + env, + napi_is_feature_enabled( + env, napi_feature_reference_all_types, &canReferenceAllTypes)); + NODE_API_ASSERT_STATUS( + env, canReferenceAllTypes, "Must be able to reference all value types."); + return napi_ok; +} + +static napi_status InitRefArray(napi_env env) { + // valueRefs array has one entry per napi_valuetype + napi_ref* valueRefs = malloc(sizeof(napi_ref) * ((int)napi_bigint + 1)); + return napi_set_instance_data(env, valueRefs, &FreeData, NULL); +} + +static napi_value CreateExternal(napi_env env, napi_callback_info info) { + napi_value result; + int* data = (int*)malloc(sizeof(int)); + *data = 42; + NODE_API_CALL(env, napi_create_external(env, data, &FreeData, NULL, &result)); + return result; +} + +static napi_value CreateRef(napi_env env, napi_callback_info info) { + napi_value argValue; + NODE_API_CALL(env, GetArgValue(env, info, &argValue)); + + napi_valuetype valueType; + NODE_API_CALL(env, napi_typeof(env, argValue, &valueType)); + uint32_t index = (uint32_t)valueType; + + napi_ref* valueRefs; + NODE_API_CALL(env, napi_get_instance_data(env, (void**)&valueRefs)); + NODE_API_CALL(env, + napi_create_reference(env, argValue, 1, valueRefs + index)); + + return ToUInt32Value(env, index); +} + +static napi_value GetRefValue(napi_env env, napi_callback_info info) { + napi_ref refValue; + NODE_API_CALL(env, GetRef(env, info, &refValue)); + napi_value value; + NODE_API_CALL(env, napi_get_reference_value(env, refValue, &value)); + return value; +} + +static napi_value Ref(napi_env env, napi_callback_info info) { + napi_ref refValue; + NODE_API_CALL(env, GetRef(env, info, &refValue)); + uint32_t refCount; + NODE_API_CALL(env, napi_reference_ref(env, refValue, &refCount)); + return ToUInt32Value(env, refCount); +} + +static napi_value Unref(napi_env env, napi_callback_info info) { + napi_ref refValue; + NODE_API_CALL(env, GetRef(env, info, &refValue)); + uint32_t refCount; + NODE_API_CALL(env, napi_reference_unref(env, refValue, &refCount)); + return ToUInt32Value(env, refCount); +} + +static napi_value DeleteRef(napi_env env, napi_callback_info info) { + napi_ref refValue; + NODE_API_CALL(env, GetRef(env, info, &refValue)); + NODE_API_CALL(env, napi_delete_reference(env, refValue)); + return NULL; +} + +static napi_value AddFinalizer(napi_env env, napi_callback_info info) { + napi_value obj; + NODE_API_CALL(env, GetArgValue(env, info, &obj)); + + napi_valuetype valueType; + NODE_API_CALL(env, napi_typeof(env, obj, &valueType)); + NODE_API_ASSERT(env, valueType == napi_object, "Argument must be an object."); + + NODE_API_CALL(env, napi_add_finalizer(env, obj, NULL, &Finalize, NULL, NULL)); + return NULL; +} + +static napi_value GetFinalizeCount(napi_env env, napi_callback_info info) { + return ToUInt32Value(env, finalizeCount); +} + +EXTERN_C_START +napi_value Init(napi_env env, napi_value exports) { + finalizeCount = 0; + NODE_API_CALL(env, CheckFeature(env)); + NODE_API_CALL(env, InitRefArray(env)); + + napi_property_descriptor properties[] = { + DECLARE_NODE_API_PROPERTY("createExternal", CreateExternal), + DECLARE_NODE_API_PROPERTY("createRef", CreateRef), + DECLARE_NODE_API_PROPERTY("getRefValue", GetRefValue), + DECLARE_NODE_API_PROPERTY("ref", Ref), + DECLARE_NODE_API_PROPERTY("unref", Unref), + DECLARE_NODE_API_PROPERTY("deleteRef", DeleteRef), + DECLARE_NODE_API_PROPERTY("addFinalizer", AddFinalizer), + DECLARE_NODE_API_PROPERTY("getFinalizeCount", GetFinalizeCount), + }; + + NODE_API_CALL( + env, + napi_define_properties( + env, exports, sizeof(properties) / sizeof(*properties), properties)); + + return exports; +} +EXTERN_C_END From e108b5e7f71ca303e4ab0908c552f1eb9e70c4f9 Mon Sep 17 00:00:00 2001 From: Vladimir Morozov Date: Mon, 10 Oct 2022 12:45:18 -0700 Subject: [PATCH 2/5] attempt to fix lint issues --- doc/api/n-api.md | 2 +- src/node_api.h | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/doc/api/n-api.md b/doc/api/n-api.md index 8524371bf7eaa8..be5eaeb613a854 100644 --- a/doc/api/n-api.md +++ b/doc/api/n-api.md @@ -5766,7 +5766,7 @@ The function checks enabled features for the module. If `feature` parameter has multiple `napi_features` bit flags, then the function returns `true` only when all the requested fatures are enabled. -See [`napi_features`][] for more details about Node-API features. +See `napi_features` for more details about Node-API features. ## Memory management diff --git a/src/node_api.h b/src/node_api.h index 4b065702d968d6..106531867dc4db 100644 --- a/src/node_api.h +++ b/src/node_api.h @@ -112,9 +112,7 @@ extern napi_features napi_module_features; priv, \ NAPI_FEATURES_PTR{0}, \ }; \ - NAPI_C_CTOR(_register_##modname) { \ - napi_module_register(&_module); \ - } \ + NAPI_C_CTOR(_register_##modname) { napi_module_register(&_module); } \ EXTERN_C_END #define NAPI_MODULE_INITIALIZER_X(base, version) \ From e6296467ba830e8f5e5b14a8b8af49eb9d7339ac Mon Sep 17 00:00:00 2001 From: Vladimir Morozov Date: Fri, 28 Oct 2022 07:55:54 -0700 Subject: [PATCH 3/5] add two reference tests that use NAPI_MODULE_INIT --- .../test_init_reference_all_types/binding.gyp | 8 + .../test_init_reference_all_types/test.js | 87 ++++++++ .../test_init_reference_all_types.c | 187 +++++++++++++++++ .../test_init_reference_obj_only/binding.gyp | 9 + .../test_init_reference_obj_only/test.js | 102 ++++++++++ .../test_init_reference_obj_only.c | 190 ++++++++++++++++++ 6 files changed, 583 insertions(+) create mode 100644 test/node-api/test_init_reference_all_types/binding.gyp create mode 100644 test/node-api/test_init_reference_all_types/test.js create mode 100644 test/node-api/test_init_reference_all_types/test_init_reference_all_types.c create mode 100644 test/node-api/test_init_reference_obj_only/binding.gyp create mode 100644 test/node-api/test_init_reference_obj_only/test.js create mode 100644 test/node-api/test_init_reference_obj_only/test_init_reference_obj_only.c diff --git a/test/node-api/test_init_reference_all_types/binding.gyp b/test/node-api/test_init_reference_all_types/binding.gyp new file mode 100644 index 00000000000000..881137d645d756 --- /dev/null +++ b/test/node-api/test_init_reference_all_types/binding.gyp @@ -0,0 +1,8 @@ +{ + "targets": [ + { + "target_name": "test_init_reference_all_types", + "sources": [ "test_init_reference_all_types.c" ] + } + ] +} diff --git a/test/node-api/test_init_reference_all_types/test.js b/test/node-api/test_init_reference_all_types/test.js new file mode 100644 index 00000000000000..d92ae774d896d0 --- /dev/null +++ b/test/node-api/test_init_reference_all_types/test.js @@ -0,0 +1,87 @@ +'use strict'; +// Flags: --expose-gc + +const { gcUntil, buildType } = require('../../common'); +const assert = require('assert'); + +// Testing API calls for references to all value types. +// This test uses NAPI_MODULE_INIT macro to initialize module. +const addon = require(`./build/${buildType}/test_init_reference_all_types`); + +async function runTests() { + let entryCount = 0; + + (() => { + // Create values of all napi_valuetype types. + const undefinedValue = undefined; + const nullValue = null; + const booleanValue = false; + const numberValue = 42; + const stringValue = 'test_string'; + const symbolValue = Symbol.for('test_symbol'); + const objectValue = { x: 1, y: 2 }; + const functionValue = (x, y) => x + y; + const externalValue = addon.createExternal(); + const bigintValue = 9007199254740991n; + + const allValues = [ + undefinedValue, + nullValue, + booleanValue, + numberValue, + stringValue, + symbolValue, + objectValue, + functionValue, + externalValue, + bigintValue, + ]; + entryCount = allValues.length; + const objectValueIndex = allValues.indexOf(objectValue); + const functionValueIndex = allValues.indexOf(functionValue); + + // Go over all values of different types, create strong ref values for + // them, read the stored values, and check how the ref count works. + for (const value of allValues) { + const index = addon.createRef(value); + const refValue = addon.getRefValue(index); + assert.strictEqual(value, refValue); + assert.strictEqual(addon.ref(index), 2); + assert.strictEqual(addon.unref(index), 1); + assert.strictEqual(addon.unref(index), 0); + } + + // The references become weak pointers when the ref count is 0. + // To be compatible with the JavaScript spec we expect these + // types to be objects and functions. + // Here we know that the GC is not run yet because the values are + // still in the allValues array. + assert.strictEqual(addon.getRefValue(objectValueIndex), objectValue); + assert.strictEqual(addon.getRefValue(functionValueIndex), functionValue); + + const objWithFinalizer = {}; + addon.addFinalizer(objWithFinalizer); + })(); + + assert.strictEqual(addon.getFinalizeCount(), 0); + await gcUntil('Wait until a finalizer is called', + () => (addon.getFinalizeCount() === 1)); + + // Create and call finalizer again to make sure that all finalizers are run. + (() => { + const objWithFinalizer = {}; + addon.addFinalizer(objWithFinalizer); + })(); + + await gcUntil('Wait until a finalizer is called again', + () => (addon.getFinalizeCount() === 1)); + + // After GC and finalizers run, all references with refCount==0 must return + // undefined value. + for (let i = 0; i < entryCount; ++i) { + const refValue = addon.getRefValue(i); + assert.strictEqual(refValue, undefined); + addon.deleteRef(i); + } +} +runTests(); diff --git a/test/node-api/test_init_reference_all_types/test_init_reference_all_types.c b/test/node-api/test_init_reference_all_types/test_init_reference_all_types.c new file mode 100644 index 00000000000000..7f5dfe1665c307 --- /dev/null +++ b/test/node-api/test_init_reference_all_types/test_init_reference_all_types.c @@ -0,0 +1,187 @@ +#define NAPI_EXPERIMENTAL +#include +#include "../../js-native-api/common.h" +#include "stdlib.h" + +#define NODE_API_ASSERT_STATUS(env, assertion, message) \ + NODE_API_ASSERT_BASE(env, assertion, message, napi_generic_failure) + +#define NODE_API_CHECK_STATUS(env, the_call) \ + do { \ + napi_status status = (the_call); \ + if (status != napi_ok) { \ + return status; \ + } \ + } while (0) + +static uint32_t finalizeCount = 0; + +static void FreeData(napi_env env, void* data, void* hint) { + NODE_API_ASSERT_RETURN_VOID(env, data != NULL, "Expects non-NULL data."); + free(data); +} + +static void Finalize(napi_env env, void* data, void* hint) { + ++finalizeCount; +} + +static napi_status GetArgValue(napi_env env, + napi_callback_info info, + napi_value* argValue) { + size_t argc = 1; + NODE_API_CHECK_STATUS( + env, napi_get_cb_info(env, info, &argc, argValue, NULL, NULL)); + + NODE_API_ASSERT_STATUS(env, argc == 1, "Expects one arg."); + return napi_ok; +} + +static napi_status GetArgValueAsIndex(napi_env env, + napi_callback_info info, + uint32_t* index) { + napi_value argValue; + NODE_API_CHECK_STATUS(env, GetArgValue(env, info, &argValue)); + + napi_valuetype valueType; + NODE_API_CHECK_STATUS(env, napi_typeof(env, argValue, &valueType)); + NODE_API_ASSERT_STATUS( + env, valueType == napi_number, "Argument must be a number."); + + return napi_get_value_uint32(env, argValue, index); +} + +static napi_status GetRef(napi_env env, + napi_callback_info info, + napi_ref* ref) { + uint32_t index; + NODE_API_CHECK_STATUS(env, GetArgValueAsIndex(env, info, &index)); + + napi_ref* refValues; + NODE_API_CHECK_STATUS(env, napi_get_instance_data(env, (void**)&refValues)); + NODE_API_ASSERT_STATUS(env, refValues != NULL, "Cannot get instance data."); + + *ref = refValues[index]; + return napi_ok; +} + +static napi_value ToUInt32Value(napi_env env, uint32_t value) { + napi_value result; + NODE_API_CALL(env, napi_create_uint32(env, value, &result)); + return result; +} + +static napi_status CheckFeature(napi_env env) { + bool canReferenceAllTypes; + NODE_API_CHECK_STATUS( + env, + napi_is_feature_enabled( + env, napi_feature_reference_all_types, &canReferenceAllTypes)); + NODE_API_ASSERT_STATUS( + env, canReferenceAllTypes, "Must be able to reference all value types."); + return napi_ok; +} + +static napi_status InitRefArray(napi_env env) { + // valueRefs array has one entry per napi_valuetype + napi_ref* valueRefs = malloc(sizeof(napi_ref) * ((int)napi_bigint + 1)); + return napi_set_instance_data(env, valueRefs, &FreeData, NULL); +} + +static napi_value CreateExternal(napi_env env, napi_callback_info info) { + napi_value result; + int* data = (int*)malloc(sizeof(int)); + *data = 42; + NODE_API_CALL(env, napi_create_external(env, data, &FreeData, NULL, &result)); + return result; +} + +static napi_value CreateRef(napi_env env, napi_callback_info info) { + napi_value argValue; + NODE_API_CALL(env, GetArgValue(env, info, &argValue)); + + napi_valuetype valueType; + NODE_API_CALL(env, napi_typeof(env, argValue, &valueType)); + uint32_t index = (uint32_t)valueType; + + napi_ref* valueRefs; + NODE_API_CALL(env, napi_get_instance_data(env, (void**)&valueRefs)); + NODE_API_CALL(env, + napi_create_reference(env, argValue, 1, valueRefs + index)); + + return ToUInt32Value(env, index); +} + +static napi_value GetRefValue(napi_env env, napi_callback_info info) { + napi_ref refValue; + NODE_API_CALL(env, GetRef(env, info, &refValue)); + napi_value value; + NODE_API_CALL(env, napi_get_reference_value(env, refValue, &value)); + return value; +} + +static napi_value Ref(napi_env env, napi_callback_info info) { + napi_ref refValue; + NODE_API_CALL(env, GetRef(env, info, &refValue)); + uint32_t refCount; + NODE_API_CALL(env, napi_reference_ref(env, refValue, &refCount)); + return ToUInt32Value(env, refCount); +} + +static napi_value Unref(napi_env env, napi_callback_info info) { + napi_ref refValue; + NODE_API_CALL(env, GetRef(env, info, &refValue)); + uint32_t refCount; + NODE_API_CALL(env, napi_reference_unref(env, refValue, &refCount)); + return ToUInt32Value(env, refCount); +} + +static napi_value DeleteRef(napi_env env, napi_callback_info info) { + napi_ref refValue; + NODE_API_CALL(env, GetRef(env, info, &refValue)); + NODE_API_CALL(env, napi_delete_reference(env, refValue)); + return NULL; +} + +static napi_value AddFinalizer(napi_env env, napi_callback_info info) { + napi_value obj; + NODE_API_CALL(env, GetArgValue(env, info, &obj)); + + napi_valuetype valueType; + NODE_API_CALL(env, napi_typeof(env, obj, &valueType)); + NODE_API_ASSERT(env, valueType == napi_object, "Argument must be an object."); + + NODE_API_CALL(env, napi_add_finalizer(env, obj, NULL, &Finalize, NULL, NULL)); + return NULL; +} + +static napi_value GetFinalizeCount(napi_env env, napi_callback_info info) { + return ToUInt32Value(env, finalizeCount); +} + +EXTERN_C_START + +NAPI_MODULE_INIT() { + finalizeCount = 0; + NODE_API_CALL(env, CheckFeature(env)); + NODE_API_CALL(env, InitRefArray(env)); + + napi_property_descriptor properties[] = { + DECLARE_NODE_API_PROPERTY("createExternal", CreateExternal), + DECLARE_NODE_API_PROPERTY("createRef", CreateRef), + DECLARE_NODE_API_PROPERTY("getRefValue", GetRefValue), + DECLARE_NODE_API_PROPERTY("ref", Ref), + DECLARE_NODE_API_PROPERTY("unref", Unref), + DECLARE_NODE_API_PROPERTY("deleteRef", DeleteRef), + DECLARE_NODE_API_PROPERTY("addFinalizer", AddFinalizer), + DECLARE_NODE_API_PROPERTY("getFinalizeCount", GetFinalizeCount), + }; + + NODE_API_CALL( + env, + napi_define_properties( + env, exports, sizeof(properties) / sizeof(*properties), properties)); + + return exports; +} + +EXTERN_C_END diff --git a/test/node-api/test_init_reference_obj_only/binding.gyp b/test/node-api/test_init_reference_obj_only/binding.gyp new file mode 100644 index 00000000000000..0278cf0b6b8db7 --- /dev/null +++ b/test/node-api/test_init_reference_obj_only/binding.gyp @@ -0,0 +1,9 @@ +{ + "targets": [ + { + "target_name": "test_init_reference_obj_only", + "sources": [ "test_init_reference_obj_only.c" ], + 'defines': [ 'NAPI_CUSTOM_FEATURES' ] + } + ] +} diff --git a/test/node-api/test_init_reference_obj_only/test.js b/test/node-api/test_init_reference_obj_only/test.js new file mode 100644 index 00000000000000..ded75e606a0d6d --- /dev/null +++ b/test/node-api/test_init_reference_obj_only/test.js @@ -0,0 +1,102 @@ +'use strict'; +// Flags: --expose-gc + +const { gcUntil, buildType } = require('../../common'); +const assert = require('assert'); + +// Testing API calls for references to only object, function, and symbol types. +// This is the reference behavior before when the +// napi_feature_reference_all_types feature is not enabled. +// This test uses NAPI_MODULE_INIT macro to initialize module. +const addon = require(`./build/${buildType}/test_init_reference_obj_only`); + +async function runTests() { + let entryCount = 0; + let refIndexes = []; + let symbolValueIndex = -1; + + (() => { + // Create values of all napi_valuetype types. + const undefinedValue = undefined; + const nullValue = null; + const booleanValue = false; + const numberValue = 42; + const stringValue = 'test_string'; + const symbolValue = Symbol.for('test_symbol'); + const objectValue = { x: 1, y: 2 }; + const functionValue = (x, y) => x + y; + const externalValue = addon.createExternal(); + const bigintValue = 9007199254740991n; + + // true if the value can be a ref. + const allEntries = [ + [ undefinedValue, false ], + [ nullValue, false ], + [ booleanValue, false ], + [ numberValue, false ], + [ stringValue, false ], + [ symbolValue, true ], + [ objectValue, true ], + [ functionValue, true ], + [ externalValue, true ], + [ bigintValue, false ], + ]; + entryCount = allEntries.length; + symbolValueIndex = allEntries.findIndex(entry => entry[0] === symbolValue); + + // Go over all values of different types, create strong ref values for + // them, read the stored values, and check how the ref count works. + for (const entry of allEntries) { + const value = entry[0]; + if (entry[1]) { + const index = addon.createRef(value); + const refValue = addon.getRefValue(index); + assert.strictEqual(value, refValue); + assert.strictEqual(addon.ref(index), 2); + assert.strictEqual(addon.unref(index), 1); + assert.strictEqual(addon.unref(index), 0); + } else { + assert.throws(() => addon.createRef(value)); + } + } + + // The references become weak pointers when the ref count is 0. + // The old reference were supported for objects, functions, and symbols. + // Here we know that the GC is not run yet because the values are + // still in the allValues array. + for (let i = 0; i < entryCount; ++i) { + if (allEntries[i][1]) { + assert.strictEqual(addon.getRefValue(i), allEntries[i][0]); + refIndexes.push(i); + } + } + + const objWithFinalizer = {}; + addon.addFinalizer(objWithFinalizer); + })(); + + assert.strictEqual(addon.getFinalizeCount(), 0); + await gcUntil('Wait until a finalizer is called', + () => (addon.getFinalizeCount() === 1)); + + // Create and call finalizer again to make sure that all finalizers are run. + (() => { + const objWithFinalizer = {}; + addon.addFinalizer(objWithFinalizer); + })(); + + await gcUntil('Wait until a finalizer is called again', + () => (addon.getFinalizeCount() === 1)); + + // After GC and finalizers run, all references with refCount==0 must return + // undefined value. + for (const index of refIndexes) { + const refValue = addon.getRefValue(index); + // Symbols do not support the weak semantic + if (symbolValueIndex !== 5) { + assert.strictEqual(refValue, undefined); + } + addon.deleteRef(index); + } +} +runTests(); diff --git a/test/node-api/test_init_reference_obj_only/test_init_reference_obj_only.c b/test/node-api/test_init_reference_obj_only/test_init_reference_obj_only.c new file mode 100644 index 00000000000000..5b2a7c39f053c4 --- /dev/null +++ b/test/node-api/test_init_reference_obj_only/test_init_reference_obj_only.c @@ -0,0 +1,190 @@ +#define NAPI_EXPERIMENTAL +#include +#include "../../js-native-api/common.h" +#include "stdlib.h" + +#define NODE_API_ASSERT_STATUS(env, assertion, message) \ + NODE_API_ASSERT_BASE(env, assertion, message, napi_generic_failure) + +#define NODE_API_CHECK_STATUS(env, the_call) \ + do { \ + napi_status status = (the_call); \ + if (status != napi_ok) { \ + return status; \ + } \ + } while (0) + +static uint32_t finalizeCount = 0; + +static void FreeData(napi_env env, void* data, void* hint) { + NODE_API_ASSERT_RETURN_VOID(env, data != NULL, "Expects non-NULL data."); + free(data); +} + +static void Finalize(napi_env env, void* data, void* hint) { + ++finalizeCount; +} + +static napi_status GetArgValue(napi_env env, + napi_callback_info info, + napi_value* argValue) { + size_t argc = 1; + NODE_API_CHECK_STATUS( + env, napi_get_cb_info(env, info, &argc, argValue, NULL, NULL)); + + NODE_API_ASSERT_STATUS(env, argc == 1, "Expects one arg."); + return napi_ok; +} + +static napi_status GetArgValueAsIndex(napi_env env, + napi_callback_info info, + uint32_t* index) { + napi_value argValue; + NODE_API_CHECK_STATUS(env, GetArgValue(env, info, &argValue)); + + napi_valuetype valueType; + NODE_API_CHECK_STATUS(env, napi_typeof(env, argValue, &valueType)); + NODE_API_ASSERT_STATUS( + env, valueType == napi_number, "Argument must be a number."); + + return napi_get_value_uint32(env, argValue, index); +} + +static napi_status GetRef(napi_env env, + napi_callback_info info, + napi_ref* ref) { + uint32_t index; + NODE_API_CHECK_STATUS(env, GetArgValueAsIndex(env, info, &index)); + + napi_ref* refValues; + NODE_API_CHECK_STATUS(env, napi_get_instance_data(env, (void**)&refValues)); + NODE_API_ASSERT_STATUS(env, refValues != NULL, "Cannot get instance data."); + + *ref = refValues[index]; + return napi_ok; +} + +static napi_value ToUInt32Value(napi_env env, uint32_t value) { + napi_value result; + NODE_API_CALL(env, napi_create_uint32(env, value, &result)); + return result; +} + +static napi_status CheckFeature(napi_env env) { + bool canReferenceAllTypes; + NODE_API_CHECK_STATUS( + env, + napi_is_feature_enabled( + env, napi_feature_reference_all_types, &canReferenceAllTypes)); + NODE_API_ASSERT_STATUS( + env, !canReferenceAllTypes, "Must not be able to reference all value types."); + return napi_ok; +} + +static napi_status InitRefArray(napi_env env) { + // valueRefs array has one entry per napi_valuetype + napi_ref* valueRefs = malloc(sizeof(napi_ref) * ((int)napi_bigint + 1)); + return napi_set_instance_data(env, valueRefs, &FreeData, NULL); +} + +static napi_value CreateExternal(napi_env env, napi_callback_info info) { + napi_value result; + int* data = (int*)malloc(sizeof(int)); + *data = 42; + NODE_API_CALL(env, napi_create_external(env, data, &FreeData, NULL, &result)); + return result; +} + +static napi_value CreateRef(napi_env env, napi_callback_info info) { + napi_value argValue; + NODE_API_CALL(env, GetArgValue(env, info, &argValue)); + + napi_valuetype valueType; + NODE_API_CALL(env, napi_typeof(env, argValue, &valueType)); + uint32_t index = (uint32_t)valueType; + + napi_ref* valueRefs; + NODE_API_CALL(env, napi_get_instance_data(env, (void**)&valueRefs)); + NODE_API_CALL(env, + napi_create_reference(env, argValue, 1, valueRefs + index)); + + return ToUInt32Value(env, index); +} + +static napi_value GetRefValue(napi_env env, napi_callback_info info) { + napi_ref refValue; + NODE_API_CALL(env, GetRef(env, info, &refValue)); + napi_value value; + NODE_API_CALL(env, napi_get_reference_value(env, refValue, &value)); + return value; +} + +static napi_value Ref(napi_env env, napi_callback_info info) { + napi_ref refValue; + NODE_API_CALL(env, GetRef(env, info, &refValue)); + uint32_t refCount; + NODE_API_CALL(env, napi_reference_ref(env, refValue, &refCount)); + return ToUInt32Value(env, refCount); +} + +static napi_value Unref(napi_env env, napi_callback_info info) { + napi_ref refValue; + NODE_API_CALL(env, GetRef(env, info, &refValue)); + uint32_t refCount; + NODE_API_CALL(env, napi_reference_unref(env, refValue, &refCount)); + return ToUInt32Value(env, refCount); +} + +static napi_value DeleteRef(napi_env env, napi_callback_info info) { + napi_ref refValue; + NODE_API_CALL(env, GetRef(env, info, &refValue)); + NODE_API_CALL(env, napi_delete_reference(env, refValue)); + return NULL; +} + +static napi_value AddFinalizer(napi_env env, napi_callback_info info) { + napi_value obj; + NODE_API_CALL(env, GetArgValue(env, info, &obj)); + + napi_valuetype valueType; + NODE_API_CALL(env, napi_typeof(env, obj, &valueType)); + NODE_API_ASSERT(env, valueType == napi_object, "Argument must be an object."); + + NODE_API_CALL(env, napi_add_finalizer(env, obj, NULL, &Finalize, NULL, NULL)); + return NULL; +} + +static napi_value GetFinalizeCount(napi_env env, napi_callback_info info) { + return ToUInt32Value(env, finalizeCount); +} + +EXTERN_C_START + +NAPI_MODULE_INIT() { + finalizeCount = 0; + NODE_API_CALL(env, CheckFeature(env)); + NODE_API_CALL(env, InitRefArray(env)); + + napi_property_descriptor properties[] = { + DECLARE_NODE_API_PROPERTY("createExternal", CreateExternal), + DECLARE_NODE_API_PROPERTY("createRef", CreateRef), + DECLARE_NODE_API_PROPERTY("getRefValue", GetRefValue), + DECLARE_NODE_API_PROPERTY("ref", Ref), + DECLARE_NODE_API_PROPERTY("unref", Unref), + DECLARE_NODE_API_PROPERTY("deleteRef", DeleteRef), + DECLARE_NODE_API_PROPERTY("addFinalizer", AddFinalizer), + DECLARE_NODE_API_PROPERTY("getFinalizeCount", GetFinalizeCount), + }; + + NODE_API_CALL( + env, + napi_define_properties( + env, exports, sizeof(properties) / sizeof(*properties), properties)); + + return exports; +} + +// Make sure that this test uses the old napi_ref behavior. +napi_features napi_module_features = napi_default_features & ~napi_feature_reference_all_types; + +EXTERN_C_END From 246e224f3ebbc49564cf730aa0ce7a66c8e910e4 Mon Sep 17 00:00:00 2001 From: Vladimir Morozov Date: Fri, 28 Oct 2022 15:26:17 -0700 Subject: [PATCH 4/5] remove _canBeWeak and change names --- doc/api/n-api.md | 47 ++++----- src/js_native_api.h | 4 +- src/js_native_api_types.h | 34 +++---- src/js_native_api_v8.cc | 23 ++--- src/js_native_api_v8.h | 24 ++--- src/node_api.cc | 8 +- src/node_api.h | 30 +++--- src/node_api_internals.h | 2 +- test/js-native-api/test_reference/binding.gyp | 2 +- .../test_reference/test_reference.c | 4 +- .../test_reference_all_types/test.js | 75 +++++++------- .../test_reference_all_types.c | 5 +- .../test_init_reference_all_types/test.js | 77 ++++++++------- .../test_init_reference_all_types.c | 4 +- .../test_init_reference_obj_only/binding.gyp | 2 +- .../test_init_reference_obj_only/test.js | 98 ++++++++++--------- .../test_init_reference_obj_only.c | 12 ++- 17 files changed, 226 insertions(+), 225 deletions(-) diff --git a/doc/api/n-api.md b/doc/api/n-api.md index be5eaeb613a854..7d755434ac1973 100644 --- a/doc/api/n-api.md +++ b/doc/api/n-api.md @@ -2046,7 +2046,7 @@ the `napi_value` in question is of the JavaScript type expected by the API. ### Enum types -#### `napi_features` +#### `node_api_features`