From b18459965cac73779fd1c66bc27de70ec4b3dd96 Mon Sep 17 00:00:00 2001 From: John French Date: Fri, 27 Sep 2019 14:28:27 -0700 Subject: [PATCH] implement Object::AddFinalizer This allows one to tie the life cycle of one JavaScript object to another. In effect, this allows for JavaScript-style closures. Fixes: https://github.com/nodejs/node-addon-api/issues/508 PR_URL: https://github.com/nodejs/node-addon-api/pull/551 Reviewed-By: Kevin Eady <8634912+KevinEady@users.noreply.github.com> --- doc/object.md | 34 ++++++++++++++++++++++++++++ napi-inl.h | 49 ++++++++++++++++++++++++++++++++++++---- napi.h | 8 +++++++ package.json | 12 +++++++++- test/binding.gyp | 1 + test/index.js | 1 + test/object/finalizer.cc | 29 ++++++++++++++++++++++++ test/object/finalizer.js | 21 +++++++++++++++++ test/object/object.cc | 7 ++++++ 9 files changed, 156 insertions(+), 6 deletions(-) create mode 100644 test/object/finalizer.cc create mode 100644 test/object/finalizer.js diff --git a/doc/object.md b/doc/object.md index de20dd8..3241054 100644 --- a/doc/object.md +++ b/doc/object.md @@ -137,6 +137,40 @@ Returns a `bool` that is true if the `Napi::Object` is an instance created by th Note: This is equivalent to the JavaScript instanceof operator. +### AddFinalizer() +```cpp +template +inline void AddFinalizer(Finalizer finalizeCallback, T* data); +``` + +- `[in] finalizeCallback`: The function to call when the object is garbage-collected. +- `[in] data`: The data to associate with the object. + +Associates `data` with the object, calling `finalizeCallback` when the object is garbage-collected. `finalizeCallback` +has the signature +```cpp +void finalizeCallback(Napi::Env env, T* data); +``` +where `data` is the pointer that was passed into the call to `AddFinalizer()`. + +### AddFinalizer() +```cpp +template +inline void AddFinalizer(Finalizer finalizeCallback, + T* data, + Hint* finalizeHint); +``` + +- `[in] data`: The data to associate with the object. +- `[in] finalizeCallback`: The function to call when the object is garbage-collected. + +Associates `data` with the object, calling `finalizeCallback` when the object is garbage-collected. An additional hint +may be given. It will also be passed to `finalizeCallback`, which has the signature +```cpp +void finalizeCallback(Napi::Env env, T* data, Hint* hint); +``` +where `data` and `hint` are the pointers that were passed into the call to `AddFinalizer()`. + ### DefineProperty() ```cpp diff --git a/napi-inl.h b/napi-inl.h index ecd1a65..d23fc83 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -24,16 +24,21 @@ namespace details { template static inline napi_status AttachData(napi_env env, napi_value obj, - FreeType* data) { + FreeType* data, + napi_finalize finalizer = nullptr, + void* hint = nullptr) { napi_value symbol, external; napi_status status = napi_create_symbol(env, nullptr, &symbol); if (status == napi_ok) { + if (finalizer == nullptr) { + finalizer = [](napi_env /*env*/, void* data, void* /*hint*/) { + delete static_cast(data); + }; + } status = napi_create_external(env, data, - [](napi_env /*env*/, void* data, void* /*hint*/) { - delete static_cast(data); - }, - nullptr, + finalizer, + hint, &external); if (status == napi_ok) { napi_property_descriptor desc = { @@ -1170,6 +1175,40 @@ inline bool Object::InstanceOf(const Function& constructor) const { return result; } +template +inline void Object::AddFinalizer(Finalizer finalizeCallback, T* data) { + details::FinalizeData* finalizeData = + new details::FinalizeData({ finalizeCallback, nullptr }); + napi_status status = + details::AttachData(_env, + *this, + data, + details::FinalizeData::Wrapper, + finalizeData); + if (status != napi_ok) { + delete finalizeData; + NAPI_THROW_IF_FAILED_VOID(_env, status); + } +} + +template +inline void Object::AddFinalizer(Finalizer finalizeCallback, + T* data, + Hint* finalizeHint) { + details::FinalizeData* finalizeData = + new details::FinalizeData({ finalizeCallback, finalizeHint }); + napi_status status = + details::AttachData(_env, + *this, + data, + details::FinalizeData::WrapperWithHint, + finalizeData); + if (status != napi_ok) { + delete finalizeData; + NAPI_THROW_IF_FAILED_VOID(_env, status); + } +} + //////////////////////////////////////////////////////////////////////////////// // External class //////////////////////////////////////////////////////////////////////////////// diff --git a/napi.h b/napi.h index 549c341..d70270e 100644 --- a/napi.h +++ b/napi.h @@ -709,6 +709,14 @@ namespace Napi { bool InstanceOf( const Function& constructor ///< Constructor function ) const; + + template + inline void AddFinalizer(Finalizer finalizeCallback, T* data); + + template + inline void AddFinalizer(Finalizer finalizeCallback, + T* data, + Hint* finalizeHint); }; template diff --git a/package.json b/package.json index ce8cc39..96ec6dd 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,17 @@ }, "directories": {}, "homepage": "https://github.com/nodejs/node-addon-api", - "keywords": ["n-api", "napi", "addon", "native", "bindings", "c", "c++", "nan", "node-addon-api"], + "keywords": [ + "n-api", + "napi", + "addon", + "native", + "bindings", + "c", + "c++", + "nan", + "node-addon-api" + ], "license": "MIT", "main": "index.js", "name": "node-addon-api", diff --git a/test/binding.gyp b/test/binding.gyp index 710beff..29878c3 100644 --- a/test/binding.gyp +++ b/test/binding.gyp @@ -27,6 +27,7 @@ 'memory_management.cc', 'name.cc', 'object/delete_property.cc', + 'object/finalizer.cc', 'object/get_property.cc', 'object/has_own_property.cc', 'object/has_property.cc', diff --git a/test/index.js b/test/index.js index 2c3092e..42dc6ba 100644 --- a/test/index.js +++ b/test/index.js @@ -30,6 +30,7 @@ let testModules = [ 'memory_management', 'name', 'object/delete_property', + 'object/finalizer', 'object/get_property', 'object/has_own_property', 'object/has_property', diff --git a/test/object/finalizer.cc b/test/object/finalizer.cc new file mode 100644 index 0000000..3518ae9 --- /dev/null +++ b/test/object/finalizer.cc @@ -0,0 +1,29 @@ +#include "napi.h" + +using namespace Napi; + +static int dummy; + +Value AddFinalizer(const CallbackInfo& info) { + ObjectReference* ref = new ObjectReference; + *ref = Persistent(Object::New(info.Env())); + info[0] + .As() + .AddFinalizer([](Napi::Env /*env*/, ObjectReference* ref) { + ref->Set("finalizerCalled", true); + delete ref; + }, ref); + return ref->Value(); +} + +Value AddFinalizerWithHint(const CallbackInfo& info) { + ObjectReference* ref = new ObjectReference; + *ref = Persistent(Object::New(info.Env())); + info[0] + .As() + .AddFinalizer([](Napi::Env /*env*/, ObjectReference* ref, int* dummy_p) { + ref->Set("finalizerCalledWithCorrectHint", dummy_p == &dummy); + delete ref; + }, ref, &dummy); + return ref->Value(); +} diff --git a/test/object/finalizer.js b/test/object/finalizer.js new file mode 100644 index 0000000..26820a0 --- /dev/null +++ b/test/object/finalizer.js @@ -0,0 +1,21 @@ +'use strict'; + +const buildType = process.config.target_defaults.default_configuration; +const assert = require('assert'); + +test(require(`../build/${buildType}/binding.node`)); +test(require(`../build/${buildType}/binding_noexcept.node`)); + +function createWeakRef(binding, bindingToTest) { + return binding.object[bindingToTest]({}); +} + +function test(binding) { + const obj1 = createWeakRef(binding, 'addFinalizer'); + global.gc(); + assert.deepStrictEqual(obj1, { finalizerCalled: true }); + + const obj2 = createWeakRef(binding, 'addFinalizerWithHint'); + global.gc(); + assert.deepStrictEqual(obj2, { finalizerCalledWithCorrectHint: true }); +} diff --git a/test/object/object.cc b/test/object/object.cc index c94a421..32f2c23 100644 --- a/test/object/object.cc +++ b/test/object/object.cc @@ -32,6 +32,10 @@ Value HasPropertyWithNapiWrapperValue(const CallbackInfo& info); Value HasPropertyWithCStyleString(const CallbackInfo& info); Value HasPropertyWithCppStyleString(const CallbackInfo& info); +// Native wrappers for testing Object::AddFinalizer() +Value AddFinalizer(const CallbackInfo& info); +Value AddFinalizerWithHint(const CallbackInfo& info); + static bool testValue = true; // Used to test void* Data() integrity struct UserDataHolder { @@ -201,5 +205,8 @@ Object InitObject(Env env) { exports["createObjectUsingMagic"] = Function::New(env, CreateObjectUsingMagic); + exports["addFinalizer"] = Function::New(env, AddFinalizer); + exports["addFinalizerWithHint"] = Function::New(env, AddFinalizerWithHint); + return exports; }