Skip to content

Commit 7ac5e00

Browse files
committed
Merge remote-tracking branch 'upstream/main' into feat/port-test-exception
# Conflicts: # PORTING.md
2 parents 95bb134 + b77ecdc commit 7ac5e00

6 files changed

Lines changed: 226 additions & 1 deletion

File tree

PORTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ Tests covering the engine-specific part of Node-API, defined in `js_native_api.h
5656
| `test_date` | Ported ✅ | Easy |
5757
| `test_error` | Ported ✅ | Medium |
5858
| `test_exception` | Ported ✅ | Medium |
59-
| `test_finalizer` | Not ported | Medium |
59+
| `test_finalizer` | Ported ✅ | Medium |
6060
| `test_function` | Ported ✅ | Medium |
6161
| `test_general` | Not ported | Hard |
6262
| `test_handle_scope` | Ported ✅ | Easy |
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# The reference suite already defines a CMake target named "test_finalizer"
2+
# (tests/js-native-api/test_reference/test_finalizer.c), and target names must
3+
# be globally unique. Use a distinct target name here but keep the output binary
4+
# named test_finalizer.node so loadAddon('test_finalizer') resolves in this dir.
5+
add_node_api_cts_experimental_addon(test_finalizer_standalone test_finalizer.c)
6+
set_target_properties(test_finalizer_standalone PROPERTIES OUTPUT_NAME test_finalizer)
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
'use strict';
2+
3+
// node_api_post_finalizer and node_api_basic_env are both experimental.
4+
if (!experimentalFeatures.postFinalizer) {
5+
skipTest();
6+
}
7+
8+
const test_finalizer = loadAddon('test_finalizer');
9+
10+
{
11+
(() => {
12+
const obj = {};
13+
test_finalizer.addFinalizer(obj);
14+
})();
15+
16+
await gcUntil(
17+
'pure finalizer',
18+
() => test_finalizer.getFinalizerCallCount() === 1,
19+
);
20+
assert.strictEqual(test_finalizer.getFinalizerCallCount(), 1);
21+
}
22+
23+
// A finalizer that calls into JS runs later via node_api_post_finalizer, so
24+
// it is observed asynchronously.
25+
{
26+
let js_is_called = false;
27+
(() => {
28+
const obj = {};
29+
test_finalizer.addFinalizerWithJS(obj, () => {
30+
js_is_called = true;
31+
});
32+
})();
33+
34+
await gcUntil(
35+
'JS-calling finalizer',
36+
() => test_finalizer.getFinalizerCallCount() === 2,
37+
);
38+
assert.strictEqual(js_is_called, true);
39+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
'use strict';
2+
3+
// A fatal finalizer aborts the process, so it runs in a spawned child
4+
// (testFatalFinalize_child.mjs) rather than tearing down the test runner.
5+
// Needs the postFinalizer API and the ability to spawn.
6+
if (!experimentalFeatures.postFinalizer || !runtimeFeatures.spawn) {
7+
skipTest();
8+
}
9+
10+
const result = await spawnTest('testFatalFinalize_child.mjs');
11+
12+
// `aborted` covers however the runtime surfaces the abort (a fatal signal on
13+
// POSIX, one of a few exit codes on Windows); the stderr match is the real
14+
// specificity guard.
15+
assert.ok(result.aborted, `Expected child to abort, got status=${result.status}`);
16+
assert.match(
17+
result.stderr,
18+
/Finalizer is calling a function that may affect GC state/,
19+
);
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Child of testFatalFinalize.js: a finalizer that illegally calls into JS from
2+
// a basic finalizer context, which makes Node-API abort the process.
3+
const test_finalizer = loadAddon('test_finalizer');
4+
5+
(() => {
6+
const obj = {};
7+
test_finalizer.addFinalizerFailOnJS(obj);
8+
})();
9+
10+
// The abort happens inside the finalizer, so this never returns; the counter
11+
// guard only matters if the illegal call is somehow accepted.
12+
let gcCount = 10;
13+
await gcUntil('fatal finalize', () => --gcCount <= 0);
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
#include <js_native_api.h>
2+
#include <stdint.h>
3+
#include <stdio.h>
4+
#include <stdlib.h>
5+
#include <string.h>
6+
#include "../common.h"
7+
#include "../entry_point.h"
8+
9+
typedef struct {
10+
int32_t finalize_count;
11+
napi_ref js_func;
12+
} FinalizerData;
13+
14+
static void finalizerOnlyCallback(node_api_basic_env env,
15+
void* finalize_data,
16+
void* finalize_hint) {
17+
FinalizerData* data = (FinalizerData*)finalize_data;
18+
int32_t count = ++data->finalize_count;
19+
20+
// It is safe to access instance data
21+
NODE_API_BASIC_CALL_RETURN_VOID(env,
22+
napi_get_instance_data(env, (void**)&data));
23+
NODE_API_BASIC_ASSERT_RETURN_VOID(count = data->finalize_count,
24+
"Expected to be the same FinalizerData");
25+
}
26+
27+
static void finalizerCallingJSCallback(napi_env env,
28+
void* finalize_data,
29+
void* finalize_hint) {
30+
napi_value js_func, undefined;
31+
FinalizerData* data = (FinalizerData*)finalize_data;
32+
NODE_API_CALL_RETURN_VOID(
33+
env, napi_get_reference_value(env, data->js_func, &js_func));
34+
NODE_API_CALL_RETURN_VOID(env, napi_get_undefined(env, &undefined));
35+
NODE_API_CALL_RETURN_VOID(
36+
env, napi_call_function(env, undefined, js_func, 0, NULL, NULL));
37+
NODE_API_CALL_RETURN_VOID(env, napi_delete_reference(env, data->js_func));
38+
data->js_func = NULL;
39+
++data->finalize_count;
40+
}
41+
42+
// Schedule async finalizer to run JavaScript-touching code.
43+
static void finalizerWithJSCallback(node_api_basic_env env,
44+
void* finalize_data,
45+
void* finalize_hint) {
46+
NODE_API_BASIC_CALL_RETURN_VOID(
47+
env,
48+
node_api_post_finalizer(
49+
env, finalizerCallingJSCallback, finalize_data, finalize_hint));
50+
}
51+
52+
static void finalizerWithFailedJSCallback(node_api_basic_env basic_env,
53+
void* finalize_data,
54+
void* finalize_hint) {
55+
// Intentionally cast to a napi_env to test the fatal failure.
56+
napi_env env = (napi_env)basic_env;
57+
napi_value obj;
58+
FinalizerData* data = (FinalizerData*)finalize_data;
59+
++data->finalize_count;
60+
NODE_API_CALL_RETURN_VOID(env, napi_create_object(env, &obj));
61+
}
62+
63+
static napi_value addFinalizer(napi_env env, napi_callback_info info) {
64+
size_t argc = 1;
65+
napi_value argv[1] = {0};
66+
FinalizerData* data;
67+
68+
NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, argv, NULL, NULL));
69+
NODE_API_CALL(env, napi_get_instance_data(env, (void**)&data));
70+
NODE_API_CALL(env,
71+
napi_add_finalizer(
72+
env, argv[0], data, finalizerOnlyCallback, NULL, NULL));
73+
return NULL;
74+
}
75+
76+
// This finalizer is going to call JavaScript from finalizer and succeed.
77+
static napi_value addFinalizerWithJS(napi_env env, napi_callback_info info) {
78+
size_t argc = 2;
79+
napi_value argv[2] = {0};
80+
napi_valuetype arg_type;
81+
FinalizerData* data;
82+
83+
NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, argv, NULL, NULL));
84+
NODE_API_CALL(env, napi_get_instance_data(env, (void**)&data));
85+
NODE_API_CALL(env, napi_typeof(env, argv[1], &arg_type));
86+
NODE_API_ASSERT(
87+
env, arg_type == napi_function, "Expected function as the second arg");
88+
NODE_API_CALL(env, napi_create_reference(env, argv[1], 1, &data->js_func));
89+
NODE_API_CALL(env,
90+
napi_add_finalizer(
91+
env, argv[0], data, finalizerWithJSCallback, NULL, NULL));
92+
return NULL;
93+
}
94+
95+
// This finalizer is going to call JavaScript from finalizer and fail.
96+
static napi_value addFinalizerFailOnJS(napi_env env, napi_callback_info info) {
97+
size_t argc = 1;
98+
napi_value argv[1] = {0};
99+
FinalizerData* data;
100+
101+
NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, argv, NULL, NULL));
102+
NODE_API_CALL(env, napi_get_instance_data(env, (void**)&data));
103+
NODE_API_CALL(
104+
env,
105+
napi_add_finalizer(
106+
env, argv[0], data, finalizerWithFailedJSCallback, NULL, NULL));
107+
return NULL;
108+
}
109+
110+
static napi_value getFinalizerCallCount(napi_env env, napi_callback_info info) {
111+
size_t argc = 1;
112+
napi_value argv[1];
113+
FinalizerData* data;
114+
napi_value result;
115+
116+
NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, argv, NULL, NULL));
117+
NODE_API_CALL(env, napi_get_instance_data(env, (void**)&data));
118+
NODE_API_CALL(env, napi_create_int32(env, data->finalize_count, &result));
119+
return result;
120+
}
121+
122+
static void finalizeData(napi_env env, void* data, void* hint) {
123+
free(data);
124+
}
125+
126+
EXTERN_C_START
127+
napi_value Init(napi_env env, napi_value exports) {
128+
FinalizerData* data = (FinalizerData*)malloc(sizeof(FinalizerData));
129+
NODE_API_ASSERT(env, data != NULL, "Failed to allocate memory");
130+
memset(data, 0, sizeof(FinalizerData));
131+
NODE_API_CALL(env, napi_set_instance_data(env, data, finalizeData, NULL));
132+
napi_property_descriptor descriptors[] = {
133+
DECLARE_NODE_API_PROPERTY("addFinalizer", addFinalizer),
134+
DECLARE_NODE_API_PROPERTY("addFinalizerWithJS", addFinalizerWithJS),
135+
DECLARE_NODE_API_PROPERTY("addFinalizerFailOnJS", addFinalizerFailOnJS),
136+
DECLARE_NODE_API_PROPERTY("getFinalizerCallCount",
137+
getFinalizerCallCount)};
138+
139+
NODE_API_CALL(
140+
env,
141+
napi_define_properties(env,
142+
exports,
143+
sizeof(descriptors) / sizeof(*descriptors),
144+
descriptors));
145+
146+
return exports;
147+
}
148+
EXTERN_C_END

0 commit comments

Comments
 (0)