From 70a98dec1b6c4c1b4f0f3ab38f1e6eb31ebe262e Mon Sep 17 00:00:00 2001 From: Vladimir Morozov Date: Fri, 30 Sep 2022 07:55:24 -0700 Subject: [PATCH] Add support for features --- src/js_native_api.h | 6 ++++++ src/js_native_api_types.h | 15 +++++++++++++++ src/js_native_api_v8.cc | 16 ++++++++++++++++ src/js_native_api_v8.h | 31 ++++++++++++++++++++++++++++++- src/node_api.cc | 29 ++++++++++++++++++++++++----- src/node_api.h | 29 +++++++++++++++++++++++++++-- src/node_api_internals.h | 3 ++- test/js-native-api/entry_point.c | 1 + 8 files changed, 121 insertions(+), 9 deletions(-) diff --git a/src/js_native_api.h b/src/js_native_api.h index 220d140d4bfe9a..619a704161ead1 100644 --- a/src/js_native_api.h +++ b/src/js_native_api.h @@ -566,6 +566,12 @@ 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_has_feature(napi_env env, + napi_feature 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..dca849d67434e5 100644 --- a/src/js_native_api_types.h +++ b/src/js_native_api_types.h @@ -108,6 +108,21 @@ 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 set them by defining array of features in NAPI_FEATURES_DEFINITON macro. +// The NAPI_FEATURES_DEFINITON is used during module initialization in +// NAPI_MODULE_X macro. +// To override the set of default feature you could define +// NAPI_FEATURES_DEFINITON macro before the node_api.h is included. +typedef enum { + // We use it as the end terminator in the sequence of enabled features. + napi_feature_none, + // Use napi_ref for all value types and not only object and functions. + napi_feature_ref_all_value_types, +} napi_feature; +#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 a07cca269f7d2d..2561144738f5b8 100644 --- a/src/js_native_api_v8.cc +++ b/src/js_native_api_v8.cc @@ -2499,6 +2499,13 @@ napi_status NAPI_CDECL napi_create_reference(napi_env env, CHECK_ARG(env, result); v8::Local v8_value = v8impl::V8LocalValueFromJsValue(value); + if (!env->HasFeature(napi_feature_ref_all_value_types)) { + if (!(v8_value->IsObject() || v8_value->IsFunction() || + v8_value->IsSymbol())) { + return napi_set_last_error(env, napi_invalid_arg); + } + } + v8impl::Reference* reference = v8impl::Reference::New(env, v8_value, initial_refcount, false); @@ -3256,3 +3263,12 @@ napi_status NAPI_CDECL napi_is_detached_arraybuffer(napi_env env, return napi_clear_last_error(env); } + +napi_status NAPI_CDECL napi_has_feature(napi_env env, + napi_feature feature, + bool* result) { + CHECK_ENV(env); + CHECK_ARG(env, result); + *result = env->HasFeature(feature); + return napi_clear_last_error(env); +} diff --git a/src/js_native_api_v8.h b/src/js_native_api_v8.h index 8d5721aba20088..180c564e3b3cee 100644 --- a/src/js_native_api_v8.h +++ b/src/js_native_api_v8.h @@ -1,6 +1,7 @@ #ifndef SRC_JS_NATIVE_API_V8_H_ #define SRC_JS_NATIVE_API_V8_H_ +#include #include "js_native_api_types.h" #include "js_native_api_v8_internals.h" @@ -50,9 +51,10 @@ class RefTracker { } // end of namespace v8impl struct napi_env__ { - explicit napi_env__(v8::Local context) + explicit napi_env__(v8::Local context, napi_feature* features) : isolate(context->GetIsolate()), context_persistent(isolate, context) { CHECK_EQ(isolate, context->GetIsolate()); + SetFeatures(features); napi_clear_last_error(this); } @@ -96,6 +98,28 @@ struct napi_env__ { CallIntoModule([&](napi_env env) { cb(env, data, hint); }); } + bool HasFeature(napi_feature feature) { + if (feature > 0 && feature < _featureCount) { + return _features[feature]; + } + return false; + } + + void SetFeatures(napi_feature* features) { + if (features == nullptr) { + return; + } + // For protection in case if the array is not zero terminated. + size_t featureCount = _featureCount; + for (napi_feature* feature = features; + *feature != napi_feature_none && featureCount > 0; + ++feature, --featureCount) { + if (*feature > 0 && *feature < _featureCount) { + _features[*feature] = true; + } + } + } + virtual void DeleteMe() { // First we must finalize those references that have `napi_finalizer` // callbacks. The reason is that addons might store other references which @@ -123,6 +147,11 @@ struct napi_env__ { int refs = 1; void* instance_data = nullptr; + // _featureCount is based on the biggest available feature value + static const size_t _featureCount = + static_cast(napi_feature_ref_all_value_types) + 1; + std::array _features = {{false}}; + protected: // Should not be deleted directly. Delete with `napi_env__::DeleteMe()` // instead. diff --git a/src/node_api.cc b/src/node_api.cc index 48b94a7c12873c..e8d9997d67f0fd 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_feature* 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_feature* 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_feature* features); + // Intercepts the Node-V8 module registration callback. Converts parameters // to NAPI equivalents and then calls the registration callback specified // by the NAPI module. @@ -604,6 +613,16 @@ 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_feature* features) { node::Environment* node_env = node::Environment::GetCurrent(context); std::string module_filename = ""; if (init == nullptr) { @@ -631,7 +650,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..4190361b4d2469 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; - void* reserved[4]; +#ifdef NAPI_EXPERIMENTAL + napi_feature* nm_features; +#else + void* reserved0; +#endif + void* reserved[3]; } napi_module; #define NAPI_MODULE_VERSION 1 @@ -73,8 +78,25 @@ typedef struct napi_module { static void fn(void) #endif +#ifdef NAPI_EXPERIMENTAL +#ifndef NAPI_FEATURES_VAR +#define NAPI_FEATURES_VAR _module_features +#endif // NAPI_FEATURES_VAR +#ifndef NAPI_FEATURES_DEFINITON +#define NAPI_FEATURES_DEFINITON \ + static napi_feature NAPI_FEATURES_VAR[] = { \ + napi_feature_ref_all_value_types, \ + napi_feature_none, \ + }; +#endif // NAPI_FEATURES_DEFINITON +#else +#define NAPI_FEATURES_VAR NULL +#define NAPI_FEATURES_DEFINITON +#endif // NAPI_EXPERIMENTAL + #define NAPI_MODULE_X(modname, regfunc, priv, flags) \ EXTERN_C_START \ + NAPI_FEATURES_DEFINITON \ static napi_module _module = { \ NAPI_MODULE_VERSION, \ flags, \ @@ -82,9 +104,12 @@ typedef struct napi_module { regfunc, \ #modname, \ priv, \ + NAPI_FEATURES_VAR, \ {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..355a425a6571cf 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_feature* features); bool can_call_into_js() const override; v8::Maybe mark_arraybuffer_as_untransferable( 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