Skip to content

Commit 65fce2b

Browse files
committed
fixup! verify dangling napi_ref in napi_wrap
1 parent 689879a commit 65fce2b

File tree

4 files changed

+141
-29
lines changed

4 files changed

+141
-29
lines changed

src/js_native_api_v8.cc

+48-24
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,12 @@ inline napi_status Unwrap(napi_env env,
218218
if (action == RemoveWrap) {
219219
CHECK(obj->DeletePrivate(context, NAPI_PRIVATE_KEY(context, wrapper))
220220
.FromJust());
221-
delete reference;
221+
if (reference->ownership() == Ownership::kUserland) {
222+
// When the wrap is been removed, the finalizer should be reset.
223+
reference->ResetFinalizer();
224+
} else {
225+
delete reference;
226+
}
222227
}
223228

224229
return GET_RETURN_STATUS(env);
@@ -245,7 +250,8 @@ class CallbackBundle {
245250
bundle->env = env;
246251

247252
v8::Local<v8::Value> cbdata = v8::External::New(env->isolate, bundle);
248-
Reference::New(env, cbdata, 0, true, Delete, bundle, nullptr);
253+
Reference::New(
254+
env, cbdata, 0, Ownership::kRuntime, Delete, bundle, nullptr);
249255
return cbdata;
250256
}
251257
napi_env env; // Necessary to invoke C++ NAPI callback
@@ -429,16 +435,21 @@ inline napi_status Wrap(napi_env env,
429435
// before then, then the finalize callback will never be invoked.)
430436
// Therefore a finalize callback is required when returning a reference.
431437
CHECK_ARG(env, finalize_cb);
432-
reference = v8impl::Reference::New(
433-
env, obj, 0, false, finalize_cb, native_object, finalize_hint);
438+
reference = v8impl::Reference::New(env,
439+
obj,
440+
0,
441+
v8impl::Ownership::kUserland,
442+
finalize_cb,
443+
native_object,
444+
finalize_hint);
434445
*result = reinterpret_cast<napi_ref>(reference);
435446
} else {
436447
// Create a self-deleting reference.
437448
reference = v8impl::Reference::New(
438449
env,
439450
obj,
440451
0,
441-
true,
452+
v8impl::Ownership::kRuntime,
442453
finalize_cb,
443454
native_object,
444455
finalize_cb == nullptr ? nullptr : finalize_hint);
@@ -456,16 +467,22 @@ inline napi_status Wrap(napi_env env,
456467

457468
} // end of anonymous namespace
458469

470+
void Finalizer::ResetFinalizer() {
471+
finalize_callback_ = nullptr;
472+
finalize_data_ = nullptr;
473+
finalize_hint_ = nullptr;
474+
}
475+
459476
// Wrapper around v8impl::Persistent that implements reference counting.
460477
RefBase::RefBase(napi_env env,
461478
uint32_t initial_refcount,
462-
bool delete_self,
479+
Ownership ownership,
463480
napi_finalize finalize_callback,
464481
void* finalize_data,
465482
void* finalize_hint)
466483
: Finalizer(env, finalize_callback, finalize_data, finalize_hint),
467484
refcount_(initial_refcount),
468-
delete_self_(delete_self) {
485+
ownership_(ownership) {
469486
Link(finalize_callback == nullptr ? &env->reflist : &env->finalizing_reflist);
470487
}
471488

@@ -480,13 +497,13 @@ RefBase::~RefBase() {
480497

481498
RefBase* RefBase::New(napi_env env,
482499
uint32_t initial_refcount,
483-
bool delete_self,
500+
Ownership ownership,
484501
napi_finalize finalize_callback,
485502
void* finalize_data,
486503
void* finalize_hint) {
487504
return new RefBase(env,
488505
initial_refcount,
489-
delete_self,
506+
ownership,
490507
finalize_callback,
491508
finalize_data,
492509
finalize_hint);
@@ -512,27 +529,29 @@ uint32_t RefBase::RefCount() {
512529
}
513530

514531
void RefBase::Finalize() {
515-
bool delete_self = delete_self_;
532+
Ownership ownership = ownership_;
516533
// Swap out the field finalize_callback so that it can not be accidentally
517534
// called more than once.
518535
napi_finalize finalize_callback = finalize_callback_;
519-
finalize_callback_ = nullptr;
536+
void* finalize_data = finalize_data_;
537+
void* finalize_hint = finalize_hint_;
538+
ResetFinalizer();
520539

521540
// Either the RefBase is going to be deleted in the finalize_callback or not,
522541
// it should be removed from the tracked list.
523542
Unlink();
524543
// 1. If the finalize_callback is present, it should either delete the
525-
// RefBase, or set the flag `delete_self`.
544+
// RefBase, or set ownership with Ownership::kRuntime.
526545
// 2. If the finalizer is not present, the RefBase can be deleted after the
527546
// call.
528547
if (finalize_callback != nullptr) {
529-
env_->CallFinalizer(finalize_callback, finalize_data_, finalize_hint_);
548+
env_->CallFinalizer(finalize_callback, finalize_data, finalize_hint);
530549
// No access to `this` after finalize_callback is called.
531550
}
532551

533-
// If the RefBase is not self-destructive, userland code should delete it.
534-
// Now delete it if it is self-destructive.
535-
if (delete_self) {
552+
// If the RefBase is not Ownership::kRuntime, userland code should delete it.
553+
// Now delete it if it is Ownership::kRuntime.
554+
if (ownership == Ownership::kRuntime) {
536555
delete this;
537556
}
538557
}
@@ -554,14 +573,14 @@ Reference::~Reference() {
554573
Reference* Reference::New(napi_env env,
555574
v8::Local<v8::Value> value,
556575
uint32_t initial_refcount,
557-
bool delete_self,
576+
Ownership ownership,
558577
napi_finalize finalize_callback,
559578
void* finalize_data,
560579
void* finalize_hint) {
561580
return new Reference(env,
562581
value,
563582
initial_refcount,
564-
delete_self,
583+
ownership,
565584
finalize_callback,
566585
finalize_data,
567586
finalize_hint);
@@ -2297,8 +2316,13 @@ napi_status NAPI_CDECL napi_create_external(napi_env env,
22972316
if (finalize_cb) {
22982317
// The Reference object will delete itself after invoking the finalizer
22992318
// callback.
2300-
v8impl::Reference::New(
2301-
env, external_value, 0, true, finalize_cb, data, finalize_hint);
2319+
v8impl::Reference::New(env,
2320+
external_value,
2321+
0,
2322+
v8impl::Ownership::kRuntime,
2323+
finalize_cb,
2324+
data,
2325+
finalize_hint);
23022326
}
23032327

23042328
*result = v8impl::JsValueFromV8LocalValue(external_value);
@@ -2407,8 +2431,8 @@ napi_status NAPI_CDECL napi_create_reference(napi_env env,
24072431
return napi_set_last_error(env, napi_invalid_arg);
24082432
}
24092433

2410-
v8impl::Reference* reference =
2411-
v8impl::Reference::New(env, v8_value, initial_refcount, false);
2434+
v8impl::Reference* reference = v8impl::Reference::New(
2435+
env, v8_value, initial_refcount, v8impl::Ownership::kUserland);
24122436

24132437
*result = reinterpret_cast<napi_ref>(reference);
24142438
return napi_clear_last_error(env);
@@ -3115,8 +3139,8 @@ napi_status NAPI_CDECL napi_set_instance_data(napi_env env,
31153139
delete old_data;
31163140
}
31173141

3118-
env->instance_data =
3119-
v8impl::RefBase::New(env, 0, true, finalize_cb, data, finalize_hint);
3142+
env->instance_data = v8impl::RefBase::New(
3143+
env, 0, v8impl::Ownership::kRuntime, finalize_cb, data, finalize_hint);
31203144

31213145
return napi_clear_last_error(env);
31223146
}

src/js_native_api_v8.h

+18-4
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,8 @@ class Finalizer {
316316
void* data() { return finalize_data_; }
317317
void* hint() { return finalize_hint_; }
318318

319+
void ResetFinalizer();
320+
319321
protected:
320322
napi_env env_;
321323
napi_finalize finalize_callback_;
@@ -337,20 +339,30 @@ class TryCatch : public v8::TryCatch {
337339
napi_env _env;
338340
};
339341

342+
// Ownership of a reference.
343+
enum class Ownership {
344+
// The reference is owned by the runtime. No userland call is needed to
345+
// destruct the reference.
346+
kRuntime,
347+
// The reference is owned by the userland. User code is responsible to delete
348+
// the reference with appropriate node-api calls.
349+
kUserland,
350+
};
351+
340352
// Wrapper around Finalizer that implements reference counting.
341353
class RefBase : public Finalizer, public RefTracker {
342354
protected:
343355
RefBase(napi_env env,
344356
uint32_t initial_refcount,
345-
bool delete_self,
357+
Ownership ownership,
346358
napi_finalize finalize_callback,
347359
void* finalize_data,
348360
void* finalize_hint);
349361

350362
public:
351363
static RefBase* New(napi_env env,
352364
uint32_t initial_refcount,
353-
bool delete_self,
365+
Ownership ownership,
354366
napi_finalize finalize_callback,
355367
void* finalize_data,
356368
void* finalize_hint);
@@ -361,12 +373,14 @@ class RefBase : public Finalizer, public RefTracker {
361373
uint32_t Unref();
362374
uint32_t RefCount();
363375

376+
Ownership ownership() { return ownership_; }
377+
364378
protected:
365379
void Finalize() override;
366380

367381
private:
368382
uint32_t refcount_;
369-
bool delete_self_;
383+
Ownership ownership_;
370384
};
371385

372386
// Wrapper around v8impl::Persistent.
@@ -379,7 +393,7 @@ class Reference : public RefBase {
379393
static Reference* New(napi_env env,
380394
v8::Local<v8::Value> value,
381395
uint32_t initial_refcount,
382-
bool delete_self,
396+
Ownership ownership,
383397
napi_finalize finalize_callback = nullptr,
384398
void* finalize_data = nullptr,
385399
void* finalize_hint = nullptr);

test/js-native-api/6_object_wrap/6_object_wrap.cc

+62-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
#include "myobject.h"
21
#include "../common.h"
2+
#include "assert.h"
3+
#include "myobject.h"
34

45
napi_ref MyObject::constructor;
56

@@ -151,9 +152,69 @@ napi_value MyObject::Multiply(napi_env env, napi_callback_info info) {
151152
return instance;
152153
}
153154

155+
// This finalizer should never be invoked.
156+
void ObjectWrapDanglingReferenceFinalizer(napi_env env,
157+
void* finalize_data,
158+
void* finalize_hint) {
159+
assert(0 && "unreachable");
160+
}
161+
162+
napi_ref dangling_ref;
163+
napi_value ObjectWrapDanglingReference(napi_env env, napi_callback_info info) {
164+
size_t argc = 1;
165+
napi_value args[1];
166+
NODE_API_CALL(env,
167+
napi_get_cb_info(env, info, &argc, args, nullptr, nullptr));
168+
169+
// Create a napi_wrap and remove it immediately, whilst leaving the out-param
170+
// ref dangling (not deleted).
171+
NODE_API_CALL(env,
172+
napi_wrap(env,
173+
args[0],
174+
nullptr,
175+
ObjectWrapDanglingReferenceFinalizer,
176+
nullptr,
177+
&dangling_ref));
178+
NODE_API_CALL(env, napi_remove_wrap(env, args[0], nullptr));
179+
180+
return args[0];
181+
}
182+
183+
napi_value ObjectWrapDanglingReferenceTest(napi_env env,
184+
napi_callback_info info) {
185+
napi_value out;
186+
napi_value ret;
187+
NODE_API_CALL(env, napi_get_reference_value(env, dangling_ref, &out));
188+
189+
if (out == nullptr) {
190+
// If the napi_ref has been invalidated, delete it.
191+
NODE_API_CALL(env, napi_delete_reference(env, dangling_ref));
192+
NODE_API_CALL(env, napi_get_boolean(env, true, &ret));
193+
} else {
194+
// The dangling napi_ref is still valid.
195+
NODE_API_CALL(env, napi_get_boolean(env, false, &ret));
196+
}
197+
return ret;
198+
}
199+
154200
EXTERN_C_START
155201
napi_value Init(napi_env env, napi_value exports) {
156202
MyObject::Init(env, exports);
203+
204+
napi_property_descriptor descriptors[] = {
205+
DECLARE_NODE_API_PROPERTY("objectWrapDanglingReference",
206+
ObjectWrapDanglingReference),
207+
DECLARE_NODE_API_PROPERTY("objectWrapDanglingReferenceTest",
208+
ObjectWrapDanglingReferenceTest),
209+
};
210+
211+
NODE_API_CALL(
212+
env,
213+
napi_define_properties(env,
214+
exports,
215+
sizeof(descriptors) / sizeof(*descriptors),
216+
descriptors));
217+
157218
return exports;
158219
}
159220
EXTERN_C_END
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Flags: --expose-gc
2+
3+
'use strict';
4+
const common = require('../../common');
5+
const addon = require(`./build/${common.buildType}/6_object_wrap`);
6+
7+
(function scope() {
8+
addon.objectWrapDanglingReference({});
9+
})();
10+
11+
common.gcUntil('object-wrap-ref', () => {
12+
return addon.objectWrapDanglingReferenceTest();
13+
});

0 commit comments

Comments
 (0)