Skip to content

Commit 171bb66

Browse files
Gabriel Schulhoflegendecas
authored andcommitted
node-api: force env shutdown deferring behavior
The finalizer normally never gets called while a reference is strong. However, during environment shutdown all finalizers must get called. In order to unify the deferring behavior with that of a regular finalization, we must force the reference to be weak when we call its finalizer during environment shutdown. Fixes: #37236 Co-authored-by: Chengzhong Wu <legendecas@gmail.com> PR-URL: #37303 Backport-PR-URL: #42512 Reviewed-By: Chengzhong Wu <legendecas@gmail.com> Reviewed-By: Michael Dawson <midawson@redhat.com>
1 parent 1193290 commit 171bb66

4 files changed

Lines changed: 86 additions & 0 deletions

File tree

‎src/js_native_api_v8.cc‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,8 +269,17 @@ class RefBase : protected Finalizer, RefTracker {
269269

270270
protected:
271271
inlinevoidFinalize(bool is_env_teardown = false) override {
272+
// During environment teardown we have to convert a strong reference to
273+
// a weak reference to force the deferring behavior if the user's finalizer
274+
// happens to delete this reference so that the code in this function that
275+
// follows the call to the user's finalizer may safely access variables from
276+
// this instance.
277+
if (is_env_teardown && RefCount() > 0) _refcount = 0;
278+
272279
if (_finalize_callback != nullptr) {
273280
_env->CallFinalizer(_finalize_callback, _finalize_data, _finalize_hint);
281+
// This ensures that we never call the finalizer twice.
282+
_finalize_callback = nullptr;
274283
}
275284

276285
// this is safe because if a request to delete the reference
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"targets": [
3+
{
4+
"target_name": "test_reference_double_free",
5+
"sources": [
6+
"../entry_point.c",
7+
"test_reference_double_free.c"
8+
]
9+
}
10+
]
11+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
'use strict';
2+
3+
// This test makes no assertions. It tests a fix without which it will crash
4+
// with a double free.
5+
6+
const{ buildType }=require('../../common');
7+
8+
constaddon=require(`./build/${buildType}/test_reference_double_free`);
9+
10+
{newaddon.MyObject(true);}
11+
{newaddon.MyObject(false);}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
#include<stdlib.h>
2+
#include<js_native_api.h>
3+
#include"../common.h"
4+
5+
staticsize_tg_call_count=0;
6+
7+
staticvoidDestructor(napi_envenv, void*data, void*nothing) {
8+
napi_ref*ref=data;
9+
NODE_API_CALL_RETURN_VOID(env, napi_delete_reference(env, *ref));
10+
free(ref);
11+
}
12+
13+
staticvoidNoDeleteDestructor(napi_envenv, void*data, void*hint) {
14+
napi_ref*ref=data;
15+
size_t*call_count=hint;
16+
17+
// This destructor must be called exactly once.
18+
if ((*call_count) >0) abort();
19+
*call_count= ((*call_count) +1);
20+
free(ref);
21+
}
22+
23+
staticnapi_valueNew(napi_envenv, napi_callback_infoinfo) {
24+
size_targc=1;
25+
napi_valuejs_this, js_delete;
26+
booldelete;
27+
napi_ref*ref=malloc(sizeof(*ref));
28+
29+
NAPI_CALL(env,
30+
napi_get_cb_info(env, info, &argc, &js_delete, &js_this, NULL));
31+
NAPI_CALL(env, napi_get_value_bool(env, js_delete, &delete));
32+
33+
if (delete) {
34+
NAPI_CALL(env,
35+
napi_wrap(env, js_this, ref, Destructor, NULL, ref));
36+
} else {
37+
NAPI_CALL(env,
38+
napi_wrap(env, js_this, ref, NoDeleteDestructor, &g_call_count, ref));
39+
}
40+
NAPI_CALL(env, napi_reference_ref(env, *ref, NULL));
41+
42+
returnjs_this;
43+
}
44+
45+
EXTERN_C_START
46+
napi_valueInit(napi_envenv, napi_valueexports) {
47+
napi_valuemyobj_ctor;
48+
NAPI_CALL(env,
49+
napi_define_class(
50+
env, "MyObject", NAPI_AUTO_LENGTH, New, NULL, 0, NULL, &myobj_ctor));
51+
NAPI_CALL(env,
52+
napi_set_named_property(env, exports, "MyObject", myobj_ctor));
53+
returnexports;
54+
}
55+
EXTERN_C_END

0 commit comments

Comments
 (0)