Skip to content

Commit 03806a0

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 <[email protected]> PR-URL: #37303 Reviewed-By: Chengzhong Wu <[email protected]> Reviewed-By: Michael Dawson <[email protected]>
1 parent bb2d85f commit 03806a0

File tree

4 files changed

+86
-0
lines changed

4 files changed

+86
-0
lines changed

src/js_native_api_v8.cc

+9
Original file line numberDiff line numberDiff line change
@@ -270,8 +270,17 @@ class RefBase : protected Finalizer, RefTracker {
270270

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

277286
// this is safe because if a request to delete the reference
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+
}
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+
const addon = require(`./build/${buildType}/test_reference_double_free`);
9+
10+
{ new addon.MyObject(true); }
11+
{ new addon.MyObject(false); }
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+
static size_t g_call_count = 0;
6+
7+
static void Destructor(napi_env env, 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+
static void NoDeleteDestructor(napi_env env, 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+
static napi_value New(napi_env env, napi_callback_info info) {
24+
size_t argc = 1;
25+
napi_value js_this, js_delete;
26+
bool delete;
27+
napi_ref* ref = malloc(sizeof(*ref));
28+
29+
NODE_API_CALL(env,
30+
napi_get_cb_info(env, info, &argc, &js_delete, &js_this, NULL));
31+
NODE_API_CALL(env, napi_get_value_bool(env, js_delete, &delete));
32+
33+
if (delete) {
34+
NODE_API_CALL(env,
35+
napi_wrap(env, js_this, ref, Destructor, NULL, ref));
36+
} else {
37+
NODE_API_CALL(env,
38+
napi_wrap(env, js_this, ref, NoDeleteDestructor, &g_call_count, ref));
39+
}
40+
NODE_API_CALL(env, napi_reference_ref(env, *ref, NULL));
41+
42+
return js_this;
43+
}
44+
45+
EXTERN_C_START
46+
napi_value Init(napi_env env, napi_value exports) {
47+
napi_value myobj_ctor;
48+
NODE_API_CALL(env,
49+
napi_define_class(
50+
env, "MyObject", NAPI_AUTO_LENGTH, New, NULL, 0, NULL, &myobj_ctor));
51+
NODE_API_CALL(env,
52+
napi_set_named_property(env, exports, "MyObject", myobj_ctor));
53+
return exports;
54+
}
55+
EXTERN_C_END

0 commit comments

Comments
 (0)