Skip to content

Commit 02c74aa

Browse files
authored
test: add nested object wrap and napi_ref test (#145)
1 parent ea4610f commit 02c74aa

File tree

5 files changed

+211
-21
lines changed

5 files changed

+211
-21
lines changed

packages/test/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,8 @@ add_test("object_exception" "./object/test_exceptions.c" OFF)
276276
add_test("objwrap" "./objwrap/myobject.cc" OFF)
277277
add_test("objwrapbasicfinalizer" "./objwrap/myobject.cc" OFF)
278278
target_compile_definitions("objwrapbasicfinalizer" PRIVATE "NAPI_EXPERIMENTAL")
279+
add_test("objnestedwrap" "./objwrap/nested_wrap.cc" OFF)
280+
target_compile_definitions("objnestedwrap" PRIVATE "NAPI_EXPERIMENTAL=10")
279281
add_test("bigint" "./bigint/binding.c" OFF)
280282
add_test("fnwrap" "./fnwrap/myobject.cc;./fnwrap/binding.cc" OFF)
281283
add_test("passwrap" "./passwrap/myobject.cc;./passwrap/binding.cc" OFF)

packages/test/objwrap/myobject.cc

Lines changed: 37 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@ napi_ref MyObject::constructor;
2828
MyObject::MyObject(double value)
2929
: value_(value), env_(nullptr), wrapper_(nullptr) {}
3030

31-
MyObject::~MyObject() { napi_delete_reference(env_, wrapper_); }
31+
MyObject::~MyObject() {
32+
napi_delete_reference(env_, wrapper_);
33+
}
3234

3335
void MyObject::Destructor(node_api_basic_env env,
3436
void* nativeObject,
@@ -44,23 +46,36 @@ void MyObject::Destructor(node_api_basic_env env,
4446

4547
void MyObject::Init(napi_env env, napi_value exports) {
4648
napi_property_descriptor properties[] = {
47-
{ "value", nullptr, nullptr, GetValue, SetValue, 0, napi_default, 0 },
48-
{ "valueReadonly", nullptr, nullptr, GetValue, nullptr, 0, napi_default,
49-
0 },
50-
DECLARE_NODE_API_PROPERTY("plusOne", PlusOne),
51-
DECLARE_NODE_API_PROPERTY("multiply", Multiply),
49+
{"value", nullptr, nullptr, GetValue, SetValue, 0, napi_default, 0},
50+
{"valueReadonly",
51+
nullptr,
52+
nullptr,
53+
GetValue,
54+
nullptr,
55+
0,
56+
napi_default,
57+
0},
58+
DECLARE_NODE_API_PROPERTY("plusOne", PlusOne),
59+
DECLARE_NODE_API_PROPERTY("multiply", Multiply),
5260
};
5361

5462
napi_value cons;
55-
NODE_API_CALL_RETURN_VOID(env, napi_define_class(
56-
env, "MyObject", -1, New, nullptr,
57-
sizeof(properties) / sizeof(napi_property_descriptor),
58-
properties, &cons));
59-
60-
NODE_API_CALL_RETURN_VOID(env, napi_create_reference(env, cons, 1, &constructor));
63+
NODE_API_CALL_RETURN_VOID(
64+
env,
65+
napi_define_class(env,
66+
"MyObject",
67+
-1,
68+
New,
69+
nullptr,
70+
sizeof(properties) / sizeof(napi_property_descriptor),
71+
properties,
72+
&cons));
6173

6274
NODE_API_CALL_RETURN_VOID(env,
63-
napi_set_named_property(env, exports, "MyObject", cons));
75+
napi_create_reference(env, cons, 1, &constructor));
76+
77+
NODE_API_CALL_RETURN_VOID(
78+
env, napi_set_named_property(env, exports, "MyObject", cons));
6479
}
6580

6681
napi_value MyObject::New(napi_env env, napi_callback_info info) {
@@ -87,12 +102,13 @@ napi_value MyObject::New(napi_env env, napi_callback_info info) {
87102
MyObject* obj = new MyObject(value);
88103

89104
obj->env_ = env;
90-
NODE_API_CALL(env, napi_wrap(env,
91-
_this,
92-
obj,
93-
MyObject::Destructor,
94-
nullptr, // finalize_hint
95-
&obj->wrapper_));
105+
NODE_API_CALL(env,
106+
napi_wrap(env,
107+
_this,
108+
obj,
109+
MyObject::Destructor,
110+
nullptr /* finalize_hint */,
111+
&obj->wrapper_));
96112

97113
return _this;
98114
}
@@ -113,7 +129,7 @@ napi_value MyObject::New(napi_env env, napi_callback_info info) {
113129
napi_value MyObject::GetValue(napi_env env, napi_callback_info info) {
114130
napi_value _this;
115131
NODE_API_CALL(env,
116-
napi_get_cb_info(env, info, nullptr, nullptr, &_this, nullptr));
132+
napi_get_cb_info(env, info, nullptr, nullptr, &_this, nullptr));
117133

118134
MyObject* obj;
119135
NODE_API_CALL(env, napi_unwrap(env, _this, reinterpret_cast<void**>(&obj)));
@@ -141,7 +157,7 @@ napi_value MyObject::SetValue(napi_env env, napi_callback_info info) {
141157
napi_value MyObject::PlusOne(napi_env env, napi_callback_info info) {
142158
napi_value _this;
143159
NODE_API_CALL(env,
144-
napi_get_cb_info(env, info, nullptr, nullptr, &_this, nullptr));
160+
napi_get_cb_info(env, info, nullptr, nullptr, &_this, nullptr));
145161

146162
MyObject* obj;
147163
NODE_API_CALL(env, napi_unwrap(env, _this, reinterpret_cast<void**>(&obj)));
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
#include "nested_wrap.h"
2+
#include "../common.h"
3+
#include "../entry_point.h"
4+
5+
#if !(!defined(__wasm__) || (defined(__EMSCRIPTEN__) || defined(__wasi__)))
6+
#include <stddef.h>
7+
extern "C" void* malloc(size_t size);
8+
extern "C" void free(void* p);
9+
10+
void* operator new(size_t size) {
11+
return malloc(size);
12+
}
13+
14+
void operator delete(void* p) noexcept {
15+
free(p);
16+
}
17+
18+
void operator delete(void* p, size_t) noexcept {
19+
free(p);
20+
}
21+
#endif
22+
23+
napi_ref NestedWrap::constructor{};
24+
static int finalization_count = 0;
25+
26+
NestedWrap::NestedWrap() {}
27+
28+
NestedWrap::~NestedWrap() {
29+
napi_delete_reference(env_, wrapper_);
30+
31+
// Delete the nested reference as well.
32+
napi_delete_reference(env_, nested_);
33+
}
34+
35+
void NestedWrap::Destructor(node_api_basic_env env,
36+
void* nativeObject,
37+
void* /*finalize_hint*/) {
38+
// Once this destructor is called, it cancels all pending
39+
// finalizers for the object by deleting the references.
40+
NestedWrap* obj = static_cast<NestedWrap*>(nativeObject);
41+
delete obj;
42+
43+
finalization_count++;
44+
}
45+
46+
void NestedWrap::Init(napi_env env, napi_value exports) {
47+
napi_value cons;
48+
NODE_API_CALL_RETURN_VOID(
49+
env,
50+
napi_define_class(
51+
env, "NestedWrap", -1, New, nullptr, 0, nullptr, &cons));
52+
53+
NODE_API_CALL_RETURN_VOID(env,
54+
napi_create_reference(env, cons, 1, &constructor));
55+
56+
NODE_API_CALL_RETURN_VOID(
57+
env, napi_set_named_property(env, exports, "NestedWrap", cons));
58+
}
59+
60+
napi_value NestedWrap::New(napi_env env, napi_callback_info info) {
61+
napi_value new_target;
62+
NODE_API_CALL(env, napi_get_new_target(env, info, &new_target));
63+
bool is_constructor = (new_target != nullptr);
64+
NODE_API_BASIC_ASSERT_BASE(
65+
is_constructor, "Constructor called without new", nullptr);
66+
67+
napi_value this_val;
68+
NODE_API_CALL(env,
69+
napi_get_cb_info(env, info, 0, nullptr, &this_val, nullptr));
70+
71+
NestedWrap* obj = new NestedWrap();
72+
73+
obj->env_ = env;
74+
NODE_API_CALL(env,
75+
napi_wrap(env,
76+
this_val,
77+
obj,
78+
NestedWrap::Destructor,
79+
nullptr /* finalize_hint */,
80+
&obj->wrapper_));
81+
82+
// Create a second napi_ref to be deleted in the destructor.
83+
NODE_API_CALL(env,
84+
napi_add_finalizer(env,
85+
this_val,
86+
obj,
87+
NestedWrap::Destructor,
88+
nullptr /* finalize_hint */,
89+
&obj->nested_));
90+
91+
return this_val;
92+
}
93+
94+
static napi_value GetFinalizerCallCount(napi_env env, napi_callback_info info) {
95+
napi_value result;
96+
NODE_API_CALL(env, napi_create_int32(env, finalization_count, &result));
97+
return result;
98+
}
99+
100+
EXTERN_C_START
101+
napi_value Init(napi_env env, napi_value exports) {
102+
NestedWrap::Init(env, exports);
103+
104+
napi_property_descriptor descriptors[] = {
105+
DECLARE_NODE_API_PROPERTY("getFinalizerCallCount", GetFinalizerCallCount),
106+
};
107+
108+
NODE_API_CALL(
109+
env,
110+
napi_define_properties(env,
111+
exports,
112+
sizeof(descriptors) / sizeof(*descriptors),
113+
descriptors));
114+
115+
return exports;
116+
}
117+
EXTERN_C_END
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#ifndef TEST_JS_NATIVE_API_6_OBJECT_WRAP_NESTED_WRAP_H_
2+
#define TEST_JS_NATIVE_API_6_OBJECT_WRAP_NESTED_WRAP_H_
3+
4+
#include <js_native_api.h>
5+
6+
/**
7+
* Test that an napi_ref can be nested inside another ObjectWrap.
8+
*
9+
* This test shows a critical case where a finalizer deletes an napi_ref
10+
* whose finalizer is also scheduled.
11+
*/
12+
13+
class NestedWrap {
14+
public:
15+
static void Init(napi_env env, napi_value exports);
16+
static void Destructor(node_api_basic_env env,
17+
void* nativeObject,
18+
void* finalize_hint);
19+
20+
private:
21+
explicit NestedWrap();
22+
~NestedWrap();
23+
24+
static napi_value New(napi_env env, napi_callback_info info);
25+
26+
static napi_ref constructor;
27+
28+
napi_env env_{};
29+
napi_ref wrapper_{};
30+
napi_ref nested_{};
31+
};
32+
33+
#endif // TEST_JS_NATIVE_API_6_OBJECT_WRAP_NESTED_WRAP_H_
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/* eslint-disable symbol-description */
2+
/* eslint-disable camelcase */
3+
'use strict'
4+
const { load } = require('../util')
5+
const common = require('../common')
6+
const assert = require('assert')
7+
8+
const p = load('objnestedwrap')
9+
module.exports = p.then(addon => {
10+
// This test verifies that ObjectWrap and napi_ref can be nested and finalized
11+
// correctly with a non-basic finalizer.
12+
(() => {
13+
let obj = new addon.NestedWrap()
14+
obj = null
15+
// Silent eslint about unused variables.
16+
assert.strictEqual(obj, null)
17+
})()
18+
19+
return common.gcUntil('object-wrap-ref', () => {
20+
return addon.getFinalizerCallCount() === 1
21+
})
22+
})

0 commit comments

Comments
 (0)