Skip to content

Commit 488bea2

Browse files
authored
Merge branch 'main' into hexbinoct/agents-md
2 parents b474e2a + 371e7c6 commit 488bea2

5 files changed

Lines changed: 288 additions & 1 deletion

File tree

PORTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ Tests covering the engine-specific part of Node-API, defined in `js_native_api.h
5555
| `test_dataview` | Ported ✅ | Medium |
5656
| `test_date` | Ported ✅ | Easy |
5757
| `test_error` | Ported ✅ | Medium |
58-
| `test_exception` | Not ported | Medium |
58+
| `test_exception` | Ported ✅ | Medium |
5959
| `test_finalizer` | Ported ✅ | Medium |
6060
| `test_function` | Ported ✅ | Medium |
6161
| `test_general` | Not ported | Hard |
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
add_node_api_cts_addon(test_exception test_exception.c)
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
'use strict';
2+
3+
const theError = new Error('Some error');
4+
5+
// The test module throws an error during Init, but in order for its exports to
6+
// not be lost, it attaches them to the error's "bindings" property. This way,
7+
// we can make sure that exceptions thrown during the module initialization
8+
// phase are propagated through require() into JavaScript.
9+
// https://github.com/nodejs/node/issues/19437
10+
const test_exception = (function() {
11+
let resultingException;
12+
try {
13+
loadAddon('test_exception');
14+
} catch (anException) {
15+
resultingException = anException;
16+
}
17+
assert.strictEqual(resultingException.message, 'Error during Init');
18+
return resultingException.binding;
19+
})();
20+
21+
{
22+
const throwTheError = () => {
23+
throw theError;
24+
};
25+
26+
// Test that the native side successfully captures the exception
27+
let returnedError = test_exception.returnException(throwTheError);
28+
assert.strictEqual(returnedError, theError);
29+
30+
// Test that the native side passes the exception through
31+
assert.throws(
32+
() => {
33+
test_exception.allowException(throwTheError);
34+
},
35+
(err) => err === theError,
36+
);
37+
38+
// Test that the exception thrown above was marked as pending
39+
// before it was handled on the JS side
40+
const exception_pending = test_exception.wasPending();
41+
assert.strictEqual(
42+
exception_pending,
43+
true,
44+
'Exception not pending as expected,' +
45+
` .wasPending() returned ${exception_pending}`,
46+
);
47+
48+
// Test that the native side does not capture a non-existing exception
49+
returnedError = test_exception.returnException(mustCall());
50+
assert.strictEqual(
51+
returnedError,
52+
undefined,
53+
'Returned error should be undefined when no exception is' +
54+
` thrown, but ${returnedError} was passed`,
55+
);
56+
}
57+
58+
{
59+
const throwTheError = class {
60+
constructor() {
61+
throw theError;
62+
}
63+
};
64+
65+
// Test that the native side successfully captures the exception
66+
let returnedError = test_exception.constructReturnException(throwTheError);
67+
assert.strictEqual(returnedError, theError);
68+
69+
// Test that the native side passes the exception through
70+
assert.throws(
71+
() => {
72+
test_exception.constructAllowException(throwTheError);
73+
},
74+
(err) => err === theError,
75+
);
76+
77+
// Test that the exception thrown above was marked as pending
78+
// before it was handled on the JS side
79+
const exception_pending = test_exception.wasPending();
80+
assert.strictEqual(
81+
exception_pending,
82+
true,
83+
'Exception not pending as expected,' +
84+
` .wasPending() returned ${exception_pending}`,
85+
);
86+
87+
// Test that the native side does not capture a non-existing exception
88+
returnedError = test_exception.constructReturnException(mustCall());
89+
assert.strictEqual(
90+
returnedError,
91+
undefined,
92+
'Returned error should be undefined when no exception is' +
93+
` thrown, but ${returnedError} was passed`,
94+
);
95+
}
96+
97+
{
98+
// Test that no exception appears that was not thrown by us
99+
let caughtError;
100+
try {
101+
test_exception.allowException(mustCall());
102+
} catch (anError) {
103+
caughtError = anError;
104+
}
105+
assert.strictEqual(
106+
caughtError,
107+
undefined,
108+
'No exception originated on the native side, but' +
109+
` ${caughtError} was passed`,
110+
);
111+
112+
// Test that the exception state remains clear when no exception is thrown
113+
const exception_pending = test_exception.wasPending();
114+
assert.strictEqual(
115+
exception_pending,
116+
false,
117+
'Exception state did not remain clear as expected,' +
118+
` .wasPending() returned ${exception_pending}`,
119+
);
120+
}
121+
122+
{
123+
// Test that no exception appears that was not thrown by us
124+
let caughtError;
125+
try {
126+
test_exception.constructAllowException(mustCall());
127+
} catch (anError) {
128+
caughtError = anError;
129+
}
130+
assert.strictEqual(
131+
caughtError,
132+
undefined,
133+
'No exception originated on the native side, but' +
134+
` ${caughtError} was passed`,
135+
);
136+
137+
// Test that the exception state remains clear when no exception is thrown
138+
const exception_pending = test_exception.wasPending();
139+
assert.strictEqual(
140+
exception_pending,
141+
false,
142+
'Exception state did not remain clear as expected,' +
143+
` .wasPending() returned ${exception_pending}`,
144+
);
145+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// A C finalizer that throws during GC surfaces as an uncaught exception. This
2+
// mirrors test_reference/test_finalizer.js, except the addon here throws from
3+
// Init; its exports are still reachable via the error's `.binding`, so a
4+
// finalizer can be installed even though initialization failed.
5+
const binding = (function() {
6+
let resultingException;
7+
try {
8+
loadAddon('test_exception');
9+
} catch (anException) {
10+
resultingException = anException;
11+
}
12+
assert.strictEqual(resultingException.message, 'Error during Init');
13+
return resultingException.binding;
14+
})();
15+
16+
let finalized = false;
17+
onUncaughtException(mustCall((err) => {
18+
assert.match(err.message, /Error during Finalize/);
19+
finalized = true;
20+
}));
21+
22+
(async function() {
23+
binding.createExternal();
24+
await gcUntil('finalizer throws', () => finalized);
25+
})().then(mustCall());
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
#include <js_native_api.h>
2+
#include "../common.h"
3+
#include "../entry_point.h"
4+
5+
static bool exceptionWasPending = false;
6+
static int num = 0x23432;
7+
8+
static napi_value returnException(napi_env env, napi_callback_info info) {
9+
size_t argc = 1;
10+
napi_value args[1];
11+
NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL));
12+
13+
napi_value global;
14+
NODE_API_CALL(env, napi_get_global(env, &global));
15+
16+
napi_value result;
17+
napi_status status = napi_call_function(env, global, args[0], 0, 0, &result);
18+
if (status == napi_pending_exception) {
19+
napi_value ex;
20+
NODE_API_CALL(env, napi_get_and_clear_last_exception(env, &ex));
21+
return ex;
22+
}
23+
24+
return NULL;
25+
}
26+
27+
static napi_value constructReturnException(napi_env env, napi_callback_info info) {
28+
size_t argc = 1;
29+
napi_value args[1];
30+
NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL));
31+
32+
napi_value result;
33+
napi_status status = napi_new_instance(env, args[0], 0, 0, &result);
34+
if (status == napi_pending_exception) {
35+
napi_value ex;
36+
NODE_API_CALL(env, napi_get_and_clear_last_exception(env, &ex));
37+
return ex;
38+
}
39+
40+
return NULL;
41+
}
42+
43+
static napi_value allowException(napi_env env, napi_callback_info info) {
44+
size_t argc = 1;
45+
napi_value args[1];
46+
NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL));
47+
48+
napi_value global;
49+
NODE_API_CALL(env, napi_get_global(env, &global));
50+
51+
napi_value result;
52+
napi_call_function(env, global, args[0], 0, 0, &result);
53+
// Ignore status and check napi_is_exception_pending() instead.
54+
55+
NODE_API_CALL(env, napi_is_exception_pending(env, &exceptionWasPending));
56+
return NULL;
57+
}
58+
59+
static napi_value constructAllowException(napi_env env, napi_callback_info info) {
60+
size_t argc = 1;
61+
napi_value args[1];
62+
NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL));
63+
64+
napi_value result;
65+
napi_new_instance(env, args[0], 0, 0, &result);
66+
// Ignore status and check napi_is_exception_pending() instead.
67+
68+
NODE_API_CALL(env, napi_is_exception_pending(env, &exceptionWasPending));
69+
return NULL;
70+
}
71+
72+
static napi_value wasPending(napi_env env, napi_callback_info info) {
73+
napi_value result;
74+
NODE_API_CALL(env, napi_get_boolean(env, exceptionWasPending, &result));
75+
76+
return result;
77+
}
78+
79+
static void finalizer(napi_env env, void *data, void *hint) {
80+
NODE_API_CALL_RETURN_VOID(env,
81+
napi_throw_error(env, NULL, "Error during Finalize"));
82+
}
83+
84+
static napi_value createExternal(napi_env env, napi_callback_info info) {
85+
napi_value external;
86+
87+
NODE_API_CALL(env,
88+
napi_create_external(env, &num, finalizer, NULL, &external));
89+
90+
return external;
91+
}
92+
93+
EXTERN_C_START
94+
napi_value Init(napi_env env, napi_value exports) {
95+
napi_property_descriptor descriptors[] = {
96+
DECLARE_NODE_API_PROPERTY("returnException", returnException),
97+
DECLARE_NODE_API_PROPERTY("allowException", allowException),
98+
DECLARE_NODE_API_PROPERTY("constructReturnException", constructReturnException),
99+
DECLARE_NODE_API_PROPERTY("constructAllowException", constructAllowException),
100+
DECLARE_NODE_API_PROPERTY("wasPending", wasPending),
101+
DECLARE_NODE_API_PROPERTY("createExternal", createExternal),
102+
};
103+
NODE_API_CALL(env, napi_define_properties(
104+
env, exports, sizeof(descriptors) / sizeof(*descriptors), descriptors));
105+
106+
napi_value error, code, message;
107+
NODE_API_CALL(env, napi_create_string_utf8(env, "Error during Init",
108+
NAPI_AUTO_LENGTH, &message));
109+
NODE_API_CALL(env, napi_create_string_utf8(env, "", NAPI_AUTO_LENGTH, &code));
110+
NODE_API_CALL(env, napi_create_error(env, code, message, &error));
111+
NODE_API_CALL(env, napi_set_named_property(env, error, "binding", exports));
112+
NODE_API_CALL(env, napi_throw(env, error));
113+
114+
return exports;
115+
}
116+
EXTERN_C_END

0 commit comments

Comments
 (0)