GeezerVison is showing github.com — node-api: get Node API version used by addon · nodejs/node@f489c67
File treeExpand file treeCollapse file tree Open diff view settings js-native-api/test_reference node-api/test_reference_by_node_api_version
Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboardExpand all lines: doc/api/n-api.md +38-23Lines changed: 38 additions & 23 deletions - Display the source diff
- Display the rich diff
| Original file line number | Diff line number | Diff line change |
|---|
@@ -1639,25 +1639,36 @@ If it is called more than once an error will be returned. | 1639 | 1639 |
| 1640 | 1640 | This API can be called even if there is a pending JavaScript exception. | 1641 | 1641 |
| 1642 | | -### References to objects with a lifespan longer than that of the native method | | 1642 | +### References to values with a lifespan longer than that of the native method | 1643 | 1643 |
| 1644 | | -In some cases an addon will need to be able to create and reference objects | | 1644 | +In some cases, an addon will need to be able to create and reference values | 1645 | 1645 | with a lifespan longer than that of a single native method invocation. For | 1646 | 1646 | example, to create a constructor and later use that constructor | 1647 | | -in a request to creates instances, it must be possible to reference | | 1647 | +in a request to create instances, it must be possible to reference | 1648 | 1648 | the constructor object across many different instance creation requests. This | 1649 | 1649 | would not be possible with a normal handle returned as a `napi_value` as | 1650 | 1650 | described in the earlier section. The lifespan of a normal handle is | 1651 | 1651 | managed by scopes and all scopes must be closed before the end of a native | 1652 | 1652 | method. | 1653 | 1653 |
| 1654 | | -Node-API provides methods to create persistent references to an object. | 1655 | | -Each persistent reference has an associated count with a value of 0 | 1656 | | -or higher. The count determines if the reference will keep | 1657 | | -the corresponding object live. References with a count of 0 do not | 1658 | | -prevent the object from being collected and are often called 'weak' | 1659 | | -references. Any count greater than 0 will prevent the object | 1660 | | -from being collected. | | 1654 | +Node-API provides methods for creating persistent references to values. | | 1655 | +Each reference has an associated count with a value of 0 or higher, | | 1656 | +which determines whether the reference will keep the corresponding value alive. | | 1657 | +References with a count of 0 do not prevent values from being collected. | | 1658 | +Values of object (object, function, external) and symbol types are becoming | | 1659 | +'weak' references and can still be accessed while they are not collected. | | 1660 | +Values of other types are released when the count becomes 0 | | 1661 | +and cannot be accessed from the reference any more. | | 1662 | +Any count greater than 0 will prevent the values from being collected. | | 1663 | + | | 1664 | +Symbol values have different flavors. The true weak reference behavior is | | 1665 | +only supported by local symbols created with the `Symbol()` constructor call. | | 1666 | +Globally registered symbols created with the `Symbol.for()` call remain | | 1667 | +always strong references because the garbage collector does not collect them. | | 1668 | +The same is true for well-known symbols such as `Symbol.iterator`. They are | | 1669 | +also never collected by the garbage collector. JavaScript's `WeakRef` and | | 1670 | +`WeakMap` types return an error when registered symbols are used, | | 1671 | +but they succeed for local and well-known symbols. | 1661 | 1672 |
| 1662 | 1673 | References can be created with an initial reference count. The count can | 1663 | 1674 | then be modified through [`napi_reference_ref`][] and | @@ -1668,6 +1679,11 @@ will return `NULL` for the returned `napi_value`. An attempt to call | 1668 | 1679 | [`napi_reference_ref`][] for a reference whose object has been collected | 1669 | 1680 | results in an error. | 1670 | 1681 |
| | 1682 | +Node-API versions 8 and earlier only allow references to be created for a | | 1683 | +limited set of value types, including object, external, function, and symbol. | | 1684 | +However, in newer Node-API versions, references can be created for any | | 1685 | +value type. | | 1686 | + | 1671 | 1687 | References must be deleted once they are no longer required by the addon. When | 1672 | 1688 | a reference is deleted, it will no longer prevent the corresponding object from | 1673 | 1689 | being collected. Failure to delete a persistent reference results in | @@ -1700,15 +1716,18 @@ NAPI_EXTERN napi_status napi_create_reference(napi_env env, | 1700 | 1716 | ``` | 1701 | 1717 |
| 1702 | 1718 | * `[in] env`: The environment that the API is invoked under. | 1703 | | -* `[in] value`: `napi_value` representing the `Object` to which we want a | 1704 | | - reference. | | 1719 | +* `[in] value`: The `napi_value` for which a reference is being created. | 1705 | 1720 | * `[in] initial_refcount`: Initial reference count for the new reference. | 1706 | 1721 | * `[out] result`: `napi_ref` pointing to the new reference. | 1707 | 1722 |
| 1708 | 1723 | Returns `napi_ok` if the API succeeded. | 1709 | 1724 |
| 1710 | 1725 | This API creates a new reference with the specified reference count | 1711 | | -to the `Object` passed in. | | 1726 | +to the value passed in. | | 1727 | + | | 1728 | +In Node-API version 8 and earlier, a reference could only be created for | | 1729 | +object, function, external, and symbol value types. However, in newer Node-API | | 1730 | +versions, a reference can be created for any value type. | 1712 | 1731 |
| 1713 | 1732 | #### `napi_delete_reference` | 1714 | 1733 |
| @@ -1787,18 +1806,15 @@ NAPI_EXTERN napi_status napi_get_reference_value(napi_env env, | 1787 | 1806 | napi_value* result); | 1788 | 1807 | ``` | 1789 | 1808 |
| 1790 | | -the `napi_value passed` in or out of these methods is a handle to the | 1791 | | -object to which the reference is related. | 1792 | | - | 1793 | 1809 | * `[in] env`: The environment that the API is invoked under. | 1794 | | -* `[in] ref`: `napi_ref` for which we requesting the corresponding `Object`. | 1795 | | -* `[out] result`: The `napi_value` for the `Object` referenced by the | 1796 | | - `napi_ref`. | | 1810 | +* `[in] ref`: The `napi_ref` for which the corresponding value is | | 1811 | + being requested. | | 1812 | +* `[out] result`: The `napi_value` referenced by the `napi_ref`. | 1797 | 1813 |
| 1798 | 1814 | Returns `napi_ok` if the API succeeded. | 1799 | 1815 |
| 1800 | 1816 | If still valid, this API returns the `napi_value` representing the | 1801 | | -JavaScript `Object` associated with the `napi_ref`. Otherwise, result | | 1817 | +JavaScript value associated with the `napi_ref`. Otherwise, result | 1802 | 1818 | will be `NULL`. | 1803 | 1819 |
| 1804 | 1820 | ### Cleanup on exit of the current Node.js environment | @@ -5069,9 +5085,8 @@ napi_status napi_define_class(napi_env env, | 5069 | 5085 | ``` | 5070 | 5086 |
| 5071 | 5087 | * `[in] env`: The environment that the API is invoked under. | 5072 | | -* `[in] utf8name`: Name of the JavaScript constructor function; When wrapping a | 5073 | | - C++ class, we recommend for clarity that this name be the same as that of | 5074 | | - the C++ class. | | 5088 | +* `[in] utf8name`: Name of the JavaScript constructor function. For clarity, | | 5089 | + it is recommended to use the C++ class name when wrapping a C++ class. | 5075 | 5090 | * `[in] length`: The length of the `utf8name` in bytes, or `NAPI_AUTO_LENGTH` | 5076 | 5091 | if it is null-terminated. | 5077 | 5092 | * `[in] constructor`: Callback function that handles constructing instances | |
Collapse file Copy file name to clipboardExpand all lines: src/api/environment.cc +11-19Lines changed: 11 additions & 19 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -872,26 +872,18 @@ void AddLinkedBinding(Environment* env, | 872 | 872 |
| 873 | 873 | void AddLinkedBinding(Environment* env, | 874 | 874 | const char* name, | 875 | | - napi_addon_register_func fn) { | | 875 | + napi_addon_register_func fn, | | 876 | + int32_t module_api_version) { | 876 | 877 | node_module mod = { | 877 | | - -1, | 878 | | - NM_F_LINKED, | 879 | | - nullptr, // nm_dso_handle | 880 | | - nullptr, // nm_filename | 881 | | - nullptr, // nm_register_func | 882 | | - [](v8::Local<v8::Object> exports, | 883 | | - v8::Local<v8::Value> module, | 884 | | - v8::Local<v8::Context> context, | 885 | | - void* priv) { | 886 | | - napi_module_register_by_symbol( | 887 | | - exports, | 888 | | - module, | 889 | | - context, | 890 | | - reinterpret_cast<napi_addon_register_func>(priv)); | 891 | | - }, | 892 | | - name, | 893 | | - reinterpret_cast<void*>(fn), | 894 | | - nullptr // nm_link | | 878 | + -1, // nm_version for Node-API | | 879 | + NM_F_LINKED, // nm_flags | | 880 | + nullptr, // nm_dso_handle | | 881 | + nullptr, // nm_filename | | 882 | + nullptr, // nm_register_func | | 883 | + get_node_api_context_register_func(env, name, module_api_version), | | 884 | + name, // nm_modname | | 885 | + reinterpret_cast<void*>(fn), // nm_priv | | 886 | + nullptr // nm_link | 895 | 887 | }; | 896 | 888 | AddLinkedBinding(env, mod); | 897 | 889 | } | |
Collapse file Copy file name to clipboardExpand all lines: src/js_native_api_v8.cc +25-6Lines changed: 25 additions & 6 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -457,6 +457,18 @@ inline napi_status Wrap(napi_env env, | 457 | 457 | return GET_RETURN_STATUS(env); | 458 | 458 | } | 459 | 459 |
| | 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 | + | 460 | 472 | } // end of anonymous namespace | 461 | 473 |
| 462 | 474 | void Finalizer::ResetFinalizer() { | @@ -551,7 +563,8 @@ void RefBase::Finalize() { | 551 | 563 | template <typename... Args> | 552 | 564 | Reference::Reference(napi_env env, v8::Local<v8::Value> value, Args&&... args) | 553 | 565 | : RefBase(env, std::forward<Args>(args)...), | 554 | | - persistent_(env->isolate, value) { | | 566 | + persistent_(env->isolate, value), | | 567 | + can_be_weak_(CanBeHeldWeakly(value)) { | 555 | 568 | if (RefCount() == 0) { | 556 | 569 | SetWeak(); | 557 | 570 | } | @@ -585,7 +598,7 @@ uint32_t Reference::Ref() { | 585 | 598 | return 0; | 586 | 599 | } | 587 | 600 | uint32_t refcount = RefBase::Ref(); | 588 | | - if (refcount == 1) { | | 601 | + if (refcount == 1 && can_be_weak_) { | 589 | 602 | persistent_.ClearWeak(); | 590 | 603 | } | 591 | 604 | return refcount; | @@ -625,7 +638,11 @@ void Reference::Finalize() { | 625 | 638 | // Mark the reference as weak and eligible for collection | 626 | 639 | // by the gc. | 627 | 640 | 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 | + } | 629 | 646 | } | 630 | 647 |
| 631 | 648 | // 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, | 2419 | 2436 | CHECK_ARG(env, result); | 2420 | 2437 |
| 2421 | 2438 | 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 | + } | 2425 | 2444 | } | 2426 | 2445 |
| 2427 | 2446 | v8impl::Reference* reference = v8impl::Reference::New( | |
Collapse file Copy file name to clipboardExpand all lines: src/js_native_api_v8.h +7-2Lines changed: 7 additions & 2 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -51,8 +51,11 @@ class Finalizer; | 51 | 51 | } // end of namespace v8impl | 52 | 52 |
| 53 | 53 | 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) { | 56 | 59 | napi_clear_last_error(this); | 57 | 60 | } | 58 | 61 |
| @@ -144,6 +147,7 @@ struct napi_env__ { | 144 | 147 | int open_callback_scopes = 0; | 145 | 148 | int refs = 1; | 146 | 149 | void* instance_data = nullptr; | | 150 | + int32_t module_api_version = NODE_API_DEFAULT_MODULE_API_VERSION; | 147 | 151 |
| 148 | 152 | protected: | 149 | 153 | // Should not be deleted directly. Delete with `napi_env__::DeleteMe()` | @@ -419,6 +423,7 @@ class Reference : public RefBase { | 419 | 423 | void SetWeak(); | 420 | 424 |
| 421 | 425 | v8impl::Persistent<v8::Value> persistent_; | | 426 | + bool can_be_weak_; | 422 | 427 | }; | 423 | 428 |
| 424 | 429 | } // end of namespace v8impl | |
Collapse file Copy file name to clipboardExpand all lines: src/node.h +5-3Lines changed: 5 additions & 3 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -1237,9 +1237,11 @@ NODE_EXTERN void AddLinkedBinding(Environment* env, | 1237 | 1237 | const char* name, | 1238 | 1238 | addon_context_register_func fn, | 1239 | 1239 | void* priv); | 1240 | | -NODE_EXTERN void AddLinkedBinding(Environment* env, | 1241 | | - const char* name, | 1242 | | - napi_addon_register_func fn); | | 1240 | +NODE_EXTERN void AddLinkedBinding( | | 1241 | + Environment* env, | | 1242 | + const char* name, | | 1243 | + napi_addon_register_func fn, | | 1244 | + int32_t module_api_version = NODE_API_DEFAULT_MODULE_API_VERSION); | 1243 | 1245 |
| 1244 | 1246 | /* Registers a callback with the passed-in Environment instance. The callback | 1245 | 1247 | * is called after the event loop exits, but before the VM is disposed. | |
Collapse file Copy file name to clipboardExpand all lines: src/node_api.cc +70-6Lines changed: 70 additions & 6 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 20 | 20 | #include <memory> | 21 | 21 |
| 22 | 22 | 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) { | 25 | 26 | CHECK_NOT_NULL(node_env()); | 26 | 27 | } | 27 | 28 |
| @@ -151,11 +152,36 @@ class BufferFinalizer : private Finalizer { | 151 | 152 | ~BufferFinalizer() { env_->Unref(); } | 152 | 153 | }; | 153 | 154 |
| | 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 | + | 154 | 167 | 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) { | 156 | 170 | node_napi_env result; | 157 | 171 |
| 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); | 159 | 185 | // TODO(addaleax): There was previously code that tried to delete the | 160 | 186 | // napi_env when its v8::Context was garbage collected; | 161 | 187 | // 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, | 623 | 649 | static_cast<const napi_module*>(priv)->nm_register_func); | 624 | 650 | } | 625 | 651 |
| | 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 | + | 626 | 689 | void napi_module_register_by_symbol(v8::Local<v8::Object> exports, | 627 | 690 | v8::Local<v8::Value> module, | 628 | 691 | v8::Local<v8::Context> context, | 629 | | - napi_addon_register_func init) { | | 692 | + napi_addon_register_func init, | | 693 | + int32_t module_api_version) { | 630 | 694 | node::Environment* node_env = node::Environment::GetCurrent(context); | 631 | 695 | std::string module_filename = ""; | 632 | 696 | if (init == nullptr) { | @@ -654,7 +718,7 @@ void napi_module_register_by_symbol(v8::Local<v8::Object> exports, | 654 | 718 | } | 655 | 719 |
| 656 | 720 | // 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); | 658 | 722 |
| 659 | 723 | napi_value _exports = nullptr; | 660 | 724 | env->CallIntoModule([&](napi_env env) { | |
|