Skip to content

Commit 8cc9e5e

Browse files
Gabriel Schulhofaddaleax
authored andcommitted
n-api: support type-tagging objects
`napi_instanceof()` is insufficient for reliably establishing the data type to which a pointer stored with `napi_wrap()` or `napi_create_external()` inside a JavaScript object points. Thus, we need a way to "mark" an object with a value that, when later retrieved, can unambiguously tell us whether it is safe to cast the pointer stored inside it to a certain structure. Such a check must survive loading/unloading/multiple instances of an addon, so we use UUIDs chosen *a priori*. Fixes: #28164 Co-authored-by: Anna Henningsen <[email protected]> PR-URL: #28237 Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Michael Dawson <[email protected]> Reviewed-By: Colin Ihrig <[email protected]> Signed-off-by: Gabriel Schulhof <[email protected]>
1 parent 212d17f commit 8cc9e5e

File tree

14 files changed

+545
-0
lines changed

14 files changed

+545
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
'targets': [
3+
{
4+
'target_name': 'binding',
5+
'sources': [ '../type-tag/binding.c' ]
6+
}
7+
]
8+
}
+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
'use strict';
2+
const common = require('../../common.js');
3+
4+
let binding;
5+
try {
6+
binding = require(`./build/${common.buildType}/binding`);
7+
} catch {
8+
console.error(`${__filename}: Binding failed to load`);
9+
process.exit(0);
10+
}
11+
12+
const bench = common.createBenchmark(main, {
13+
n: [1e5, 1e6, 1e7],
14+
});
15+
16+
function main({ n }) {
17+
binding.checkObjectTag(n, bench, bench.start, bench.end);
18+
}

benchmark/napi/type-tag/binding.c

+84
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
#include <assert.h>
2+
#define NAPI_EXPERIMENTAL
3+
#include <node_api.h>
4+
5+
#define NAPI_CALL(call) \
6+
do { \
7+
napi_status status = call; \
8+
assert(status == napi_ok && #call " failed"); \
9+
} while (0);
10+
11+
#define EXPORT_FUNC(env, exports, name, func) \
12+
do { \
13+
napi_value js_func; \
14+
NAPI_CALL(napi_create_function((env), \
15+
(name), \
16+
NAPI_AUTO_LENGTH, \
17+
(func), \
18+
NULL, \
19+
&js_func)); \
20+
NAPI_CALL(napi_set_named_property((env), \
21+
(exports), \
22+
(name), \
23+
js_func)); \
24+
} while (0);
25+
26+
static const napi_type_tag tag = {
27+
0xe7ecbcd5954842f6, 0x9e75161c9bf27282
28+
};
29+
30+
static napi_value TagObject(napi_env env, napi_callback_info info) {
31+
size_t argc = 4;
32+
napi_value argv[4];
33+
uint32_t n;
34+
uint32_t index;
35+
napi_handle_scope scope;
36+
37+
NAPI_CALL(napi_get_cb_info(env, info, &argc, argv, NULL, NULL));
38+
NAPI_CALL(napi_get_value_uint32(env, argv[0], &n));
39+
NAPI_CALL(napi_open_handle_scope(env, &scope));
40+
napi_value objects[n];
41+
for (index = 0; index < n; index++) {
42+
NAPI_CALL(napi_create_object(env, &objects[index]));
43+
}
44+
45+
// Time the object tag creation.
46+
NAPI_CALL(napi_call_function(env, argv[1], argv[2], 0, NULL, NULL));
47+
for (index = 0; index < n; index++) {
48+
NAPI_CALL(napi_type_tag_object(env, objects[index], &tag));
49+
}
50+
NAPI_CALL(napi_call_function(env, argv[1], argv[3], 1, &argv[0], NULL));
51+
52+
NAPI_CALL(napi_close_handle_scope(env, scope));
53+
return NULL;
54+
}
55+
56+
static napi_value CheckObjectTag(napi_env env, napi_callback_info info) {
57+
size_t argc = 4;
58+
napi_value argv[4];
59+
uint32_t n;
60+
uint32_t index;
61+
bool is_of_type;
62+
63+
NAPI_CALL(napi_get_cb_info(env, info, &argc, argv, NULL, NULL));
64+
NAPI_CALL(napi_get_value_uint32(env, argv[0], &n));
65+
napi_value object;
66+
NAPI_CALL(napi_create_object(env, &object));
67+
NAPI_CALL(napi_type_tag_object(env, object, &tag));
68+
69+
// Time the object tag checking.
70+
NAPI_CALL(napi_call_function(env, argv[1], argv[2], 0, NULL, NULL));
71+
for (index = 0; index < n; index++) {
72+
NAPI_CALL(napi_check_object_type_tag(env, object, &tag, &is_of_type));
73+
assert(is_of_type && " type mismatch");
74+
}
75+
NAPI_CALL(napi_call_function(env, argv[1], argv[3], 1, &argv[0], NULL));
76+
77+
return NULL;
78+
}
79+
80+
NAPI_MODULE_INIT() {
81+
EXPORT_FUNC(env, exports, "tagObject", TagObject);
82+
EXPORT_FUNC(env, exports, "checkObjectTag", CheckObjectTag);
83+
return exports;
84+
}

benchmark/napi/type-tag/binding.gyp

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
'targets': [
3+
{
4+
'target_name': 'binding',
5+
'sources': [ 'binding.c' ]
6+
}
7+
]
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
'use strict';
2+
const common = require('../../common.js');
3+
4+
let binding;
5+
try {
6+
binding = require(`./build/${common.buildType}/binding`);
7+
} catch {
8+
console.error(`${__filename}: Binding failed to load`);
9+
process.exit(0);
10+
}
11+
12+
const bench = common.createBenchmark(main, {
13+
n: [1e5, 1e6, 1e7],
14+
});
15+
16+
function main({ n }) {
17+
binding.checkObjectTag(n, bench, bench.start, bench.end);
18+
}

benchmark/napi/type-tag/index.js

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
'use strict';
2+
const common = require('../../common.js');
3+
4+
let binding;
5+
try {
6+
binding = require(`./build/${common.buildType}/binding`);
7+
} catch {
8+
console.error(`${__filename}: Binding failed to load`);
9+
process.exit(0);
10+
}
11+
12+
const bench = common.createBenchmark(main, {
13+
n: [1e3, 1e4, 1e5],
14+
});
15+
16+
function main({ n }) {
17+
binding.tagObject(n, bench, bench.start, bench.end);
18+
}

0 commit comments

Comments
 (0)