Skip to content

node-api: add nested object wrap and napi_ref test #57981

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 25, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 16 additions & 5 deletions test/js-native-api/6_object_wrap/binding.gyp
Original file line number Diff line number Diff line change
@@ -1,17 +1,28 @@
{
"targets": [
{
"target_name": "6_object_wrap",
"target_name": "myobject",
"sources": [
"6_object_wrap.cc"
"myobject.cc",
"myobject.h",
]
},
{
"target_name": "6_object_wrap_basic_finalizer",
"target_name": "myobject_basic_finalizer",
"defines": [ "NAPI_EXPERIMENTAL" ],
"sources": [
"6_object_wrap.cc"
"myobject.cc",
"myobject.h",
]
}
},
{
"target_name": "nested_wrap",
# Test without basic finalizers as it schedules differently.
"defines": [ "NAPI_VERSION=10" ],
"sources": [
"nested_wrap.cc",
"nested_wrap.h",
],
},
]
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#include "myobject.h"
#include "../common.h"
#include "../entry_point.h"
#include "assert.h"
#include "myobject.h"

typedef int32_t FinalizerData;

Expand All @@ -10,7 +10,9 @@ napi_ref MyObject::constructor;
MyObject::MyObject(double value)
: value_(value), env_(nullptr), wrapper_(nullptr) {}

MyObject::~MyObject() { napi_delete_reference(env_, wrapper_); }
MyObject::~MyObject() {
napi_delete_reference(env_, wrapper_);
}

void MyObject::Destructor(node_api_basic_env env,
void* nativeObject,
Expand All @@ -26,24 +28,36 @@ void MyObject::Destructor(node_api_basic_env env,

void MyObject::Init(napi_env env, napi_value exports) {
napi_property_descriptor properties[] = {
{ "value", nullptr, nullptr, GetValue, SetValue, 0, napi_default, 0 },
{ "valueReadonly", nullptr, nullptr, GetValue, nullptr, 0, napi_default,
0 },
DECLARE_NODE_API_PROPERTY("plusOne", PlusOne),
DECLARE_NODE_API_PROPERTY("multiply", Multiply),
{"value", nullptr, nullptr, GetValue, SetValue, 0, napi_default, 0},
{"valueReadonly",
nullptr,
nullptr,
GetValue,
nullptr,
0,
napi_default,
0},
DECLARE_NODE_API_PROPERTY("plusOne", PlusOne),
DECLARE_NODE_API_PROPERTY("multiply", Multiply),
};

napi_value cons;
NODE_API_CALL_RETURN_VOID(env, napi_define_class(
env, "MyObject", -1, New, nullptr,
sizeof(properties) / sizeof(napi_property_descriptor),
properties, &cons));
NODE_API_CALL_RETURN_VOID(
env,
napi_define_class(env,
"MyObject",
-1,
New,
nullptr,
sizeof(properties) / sizeof(napi_property_descriptor),
properties,
&cons));

NODE_API_CALL_RETURN_VOID(env,
napi_create_reference(env, cons, 1, &constructor));
napi_create_reference(env, cons, 1, &constructor));

NODE_API_CALL_RETURN_VOID(env,
napi_set_named_property(env, exports, "MyObject", cons));
NODE_API_CALL_RETURN_VOID(
env, napi_set_named_property(env, exports, "MyObject", cons));
}

napi_value MyObject::New(napi_env env, napi_callback_info info) {
Expand Down Expand Up @@ -71,8 +85,12 @@ napi_value MyObject::New(napi_env env, napi_callback_info info) {

obj->env_ = env;
NODE_API_CALL(env,
napi_wrap(env, _this, obj, MyObject::Destructor,
nullptr /* finalize_hint */, &obj->wrapper_));
napi_wrap(env,
_this,
obj,
MyObject::Destructor,
nullptr /* finalize_hint */,
&obj->wrapper_));

return _this;
}
Expand All @@ -93,7 +111,7 @@ napi_value MyObject::New(napi_env env, napi_callback_info info) {
napi_value MyObject::GetValue(napi_env env, napi_callback_info info) {
napi_value _this;
NODE_API_CALL(env,
napi_get_cb_info(env, info, nullptr, nullptr, &_this, nullptr));
napi_get_cb_info(env, info, nullptr, nullptr, &_this, nullptr));

MyObject* obj;
NODE_API_CALL(env, napi_unwrap(env, _this, reinterpret_cast<void**>(&obj)));
Expand Down Expand Up @@ -121,7 +139,7 @@ napi_value MyObject::SetValue(napi_env env, napi_callback_info info) {
napi_value MyObject::PlusOne(napi_env env, napi_callback_info info) {
napi_value _this;
NODE_API_CALL(env,
napi_get_cb_info(env, info, nullptr, nullptr, &_this, nullptr));
napi_get_cb_info(env, info, nullptr, nullptr, &_this, nullptr));

MyObject* obj;
NODE_API_CALL(env, napi_unwrap(env, _this, reinterpret_cast<void**>(&obj)));
Expand Down
99 changes: 99 additions & 0 deletions test/js-native-api/6_object_wrap/nested_wrap.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
#include "nested_wrap.h"
#include "../common.h"
#include "../entry_point.h"

napi_ref NestedWrap::constructor{};
static int finalization_count = 0;

NestedWrap::NestedWrap() {}

NestedWrap::~NestedWrap() {
napi_delete_reference(env_, wrapper_);

// Delete the nested reference as well.
napi_delete_reference(env_, nested_);
}

void NestedWrap::Destructor(node_api_basic_env env,
void* nativeObject,
void* /*finalize_hint*/) {
// Once this destructor is called, it cancels all pending
// finalizers for the object by deleting the references.
NestedWrap* obj = static_cast<NestedWrap*>(nativeObject);
delete obj;

finalization_count++;
}

void NestedWrap::Init(napi_env env, napi_value exports) {
napi_value cons;
NODE_API_CALL_RETURN_VOID(
env,
napi_define_class(
env, "NestedWrap", -1, New, nullptr, 0, nullptr, &cons));

NODE_API_CALL_RETURN_VOID(env,
napi_create_reference(env, cons, 1, &constructor));

NODE_API_CALL_RETURN_VOID(
env, napi_set_named_property(env, exports, "NestedWrap", cons));
}

napi_value NestedWrap::New(napi_env env, napi_callback_info info) {
napi_value new_target;
NODE_API_CALL(env, napi_get_new_target(env, info, &new_target));
bool is_constructor = (new_target != nullptr);
NODE_API_BASIC_ASSERT_BASE(
is_constructor, "Constructor called without new", nullptr);

napi_value this_val;
NODE_API_CALL(env,
napi_get_cb_info(env, info, 0, nullptr, &this_val, nullptr));

NestedWrap* obj = new NestedWrap();

obj->env_ = env;
NODE_API_CALL(env,
napi_wrap(env,
this_val,
obj,
NestedWrap::Destructor,
nullptr /* finalize_hint */,
&obj->wrapper_));

// Create a second napi_ref to be deleted in the destructor.
NODE_API_CALL(env,
napi_add_finalizer(env,
this_val,
obj,
NestedWrap::Destructor,
nullptr /* finalize_hint */,
&obj->nested_));

return this_val;
}

static napi_value GetFinalizerCallCount(napi_env env, napi_callback_info info) {
napi_value result;
NODE_API_CALL(env, napi_create_int32(env, finalization_count, &result));
return result;
}

EXTERN_C_START
napi_value Init(napi_env env, napi_value exports) {
NestedWrap::Init(env, exports);

napi_property_descriptor descriptors[] = {
DECLARE_NODE_API_PROPERTY("getFinalizerCallCount", GetFinalizerCallCount),
};

NODE_API_CALL(
env,
napi_define_properties(env,
exports,
sizeof(descriptors) / sizeof(*descriptors),
descriptors));

return exports;
}
EXTERN_C_END
33 changes: 33 additions & 0 deletions test/js-native-api/6_object_wrap/nested_wrap.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#ifndef TEST_JS_NATIVE_API_6_OBJECT_WRAP_NESTED_WRAP_H_
#define TEST_JS_NATIVE_API_6_OBJECT_WRAP_NESTED_WRAP_H_

#include <js_native_api.h>

/**
* Test that an napi_ref can be nested inside another ObjectWrap.
*
* This test shows a critical case where a finalizer deletes an napi_ref
* whose finalizer is also scheduled.
*/

class NestedWrap {
public:
static void Init(napi_env env, napi_value exports);
static void Destructor(node_api_basic_env env,
void* nativeObject,
void* finalize_hint);

private:
explicit NestedWrap();
~NestedWrap();

static napi_value New(napi_env env, napi_callback_info info);

static napi_ref constructor;

napi_env env_{};
napi_ref wrapper_{};
napi_ref nested_{};
};

#endif // TEST_JS_NATIVE_API_6_OBJECT_WRAP_NESTED_WRAP_H_
20 changes: 20 additions & 0 deletions test/js-native-api/6_object_wrap/nested_wrap.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Flags: --expose-gc

'use strict';
const common = require('../../common');
const { gcUntil } = require('../../common/gc');
const assert = require('assert');
const addon = require(`./build/${common.buildType}/nested_wrap`);

// This test verifies that ObjectWrap and napi_ref can be nested and finalized
// correctly with a non-basic finalizer.
(() => {
let obj = new addon.NestedWrap();
obj = null;
// Silent eslint about unused variables.
assert.strictEqual(obj, null);
})();

gcUntil('object-wrap-ref', () => {
return addon.getFinalizerCallCount() === 1;
});
2 changes: 1 addition & 1 deletion test/js-native-api/6_object_wrap/test-basic-finalizer.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
'use strict';
const common = require('../../common');
const assert = require('assert');
const addon = require(`./build/${common.buildType}/6_object_wrap_basic_finalizer`);
const addon = require(`./build/${common.buildType}/myobject_basic_finalizer`);

// This test verifies that ObjectWrap can be correctly finalized with a node_api_basic_finalizer
// in the current JS loop tick
Expand Down
2 changes: 1 addition & 1 deletion test/js-native-api/6_object_wrap/test-object-wrap-ref.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

'use strict';
const common = require('../../common');
const addon = require(`./build/${common.buildType}/6_object_wrap`);
const addon = require(`./build/${common.buildType}/myobject`);
const { gcUntil } = require('../../common/gc');

(function scope() {
Expand Down
2 changes: 1 addition & 1 deletion test/js-native-api/6_object_wrap/test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use strict';
const common = require('../../common');
const assert = require('assert');
const addon = require(`./build/${common.buildType}/6_object_wrap`);
const addon = require(`./build/${common.buildType}/myobject`);

const getterOnlyErrorRE =
/^TypeError: Cannot set property .* of #<.*> which has only a getter$/;
Expand Down
Loading