Skip to content

Commit b4365c1

Browse files
node-api: extend type-tagging to externals
Since externals behave as JavaScript objects on the JavaScript side, allow them to be type-tagged. Signed-off-by: Gabriel Schulhof <[email protected]> PR-URL: #47141 Reviewed-By: Michael Dawson <[email protected]> Reviewed-By: Chengzhong Wu <[email protected]>
1 parent 22537f3 commit b4365c1

File tree

3 files changed

+117
-45
lines changed

3 files changed

+117
-45
lines changed

doc/api/n-api.md

+17-14
Original file line numberDiff line numberDiff line change
@@ -733,13 +733,13 @@ napiVersion: 8
733733
-->
734734

735735
A 128-bit value stored as two unsigned 64-bit integers. It serves as a UUID
736-
with which JavaScript objects can be "tagged" in order to ensure that they are
737-
of a certain type. This is a stronger check than [`napi_instanceof`][], because
738-
the latter can report a false positive if the object's prototype has been
739-
manipulated. Type-tagging is most useful in conjunction with [`napi_wrap`][]
740-
because it ensures that the pointer retrieved from a wrapped object can be
741-
safely cast to the native type corresponding to the type tag that had been
742-
previously applied to the JavaScript object.
736+
with which JavaScript objects or [externals][] can be "tagged" in order to
737+
ensure that they are of a certain type. This is a stronger check than
738+
[`napi_instanceof`][], because the latter can report a false positive if the
739+
object's prototype has been manipulated. Type-tagging is most useful in
740+
conjunction with [`napi_wrap`][] because it ensures that the pointer retrieved
741+
from a wrapped object can be safely cast to the native type corresponding to the
742+
type tag that had been previously applied to the JavaScript object.
743743

744744
```c
745745
typedef struct {
@@ -4969,7 +4969,7 @@ To this end, Node-API provides type-tagging capabilities.
49694969

49704970
A type tag is a 128-bit integer unique to the addon. Node-API provides the
49714971
`napi_type_tag` structure for storing a type tag. When such a value is passed
4972-
along with a JavaScript object stored in a `napi_value` to
4972+
along with a JavaScript object or [external][] stored in a `napi_value` to
49734973
`napi_type_tag_object()`, the JavaScript object will be "marked" with the
49744974
type tag. The "mark" is invisible on the JavaScript side. When a JavaScript
49754975
object arrives into a native binding, `napi_check_object_type_tag()` can be used
@@ -5255,15 +5255,15 @@ napi_status napi_type_tag_object(napi_env env,
52555255
```
52565256

52575257
* `[in] env`: The environment that the API is invoked under.
5258-
* `[in] js_object`: The JavaScript object to be marked.
5258+
* `[in] js_object`: The JavaScript object or [external][] to be marked.
52595259
* `[in] type_tag`: The tag with which the object is to be marked.
52605260

52615261
Returns `napi_ok` if the API succeeded.
52625262

5263-
Associates the value of the `type_tag` pointer with the JavaScript object.
5264-
`napi_check_object_type_tag()` can then be used to compare the tag that was
5265-
attached to the object with one owned by the addon to ensure that the object
5266-
has the right type.
5263+
Associates the value of the `type_tag` pointer with the JavaScript object or
5264+
[external][]. `napi_check_object_type_tag()` can then be used to compare the tag
5265+
that was attached to the object with one owned by the addon to ensure that the
5266+
object has the right type.
52675267

52685268
If the object already has an associated type tag, this API will return
52695269
`napi_invalid_arg`.
@@ -5285,7 +5285,8 @@ napi_status napi_check_object_type_tag(napi_env env,
52855285
```
52865286

52875287
* `[in] env`: The environment that the API is invoked under.
5288-
* `[in] js_object`: The JavaScript object whose type tag to examine.
5288+
* `[in] js_object`: The JavaScript object or [external][] whose type tag to
5289+
examine.
52895290
* `[in] type_tag`: The tag with which to compare any tag found on the object.
52905291
* `[out] result`: Whether the type tag given matched the type tag on the
52915292
object. `false` is also returned if no type tag was found on the object.
@@ -6455,6 +6456,8 @@ the add-on's file name during loading.
64556456
[async_hooks `type`]: async_hooks.md#type
64566457
[context-aware addons]: addons.md#context-aware-addons
64576458
[docs]: https://github.com/nodejs/node-addon-api#api-documentation
6459+
[external]: #napi_create_external
6460+
[externals]: #napi_create_external
64586461
[global scope]: globals.md
64596462
[gyp-next]: https://github.com/nodejs/gyp-next
64606463
[module scope]: modules.md#the-module-scope

test/js-native-api/test_object/test.js

+23
Original file line numberDiff line numberDiff line change
@@ -165,12 +165,26 @@ assert.strictEqual(newObject.test_string, 'test string');
165165
const obj2 = test_object.TypeTaggedInstance(1);
166166
const obj3 = test_object.TypeTaggedInstance(2);
167167
const obj4 = test_object.TypeTaggedInstance(3);
168+
const external = test_object.TypeTaggedExternal(2);
169+
const plainExternal = test_object.PlainExternal();
170+
171+
// Verify that we do not allow type tag indices greater than the largest
172+
// available index.
173+
assert.throws(() => test_object.TypeTaggedInstance(39), {
174+
name: 'RangeError',
175+
message: 'Invalid type index',
176+
});
177+
assert.throws(() => test_object.TypeTaggedExternal(39), {
178+
name: 'RangeError',
179+
message: 'Invalid type index',
180+
});
168181

169182
// Verify that type tags are correctly accepted.
170183
assert.strictEqual(test_object.CheckTypeTag(0, obj1), true);
171184
assert.strictEqual(test_object.CheckTypeTag(1, obj2), true);
172185
assert.strictEqual(test_object.CheckTypeTag(2, obj3), true);
173186
assert.strictEqual(test_object.CheckTypeTag(3, obj4), true);
187+
assert.strictEqual(test_object.CheckTypeTag(2, external), true);
174188

175189
// Verify that wrongly tagged objects are rejected.
176190
assert.strictEqual(test_object.CheckTypeTag(0, obj2), false);
@@ -180,10 +194,19 @@ assert.strictEqual(newObject.test_string, 'test string');
180194
assert.strictEqual(test_object.CheckTypeTag(2, obj4), false);
181195
assert.strictEqual(test_object.CheckTypeTag(3, obj3), false);
182196
assert.strictEqual(test_object.CheckTypeTag(4, obj3), false);
197+
assert.strictEqual(test_object.CheckTypeTag(0, external), false);
198+
assert.strictEqual(test_object.CheckTypeTag(1, external), false);
199+
assert.strictEqual(test_object.CheckTypeTag(3, external), false);
200+
assert.strictEqual(test_object.CheckTypeTag(4, external), false);
183201

184202
// Verify that untagged objects are rejected.
185203
assert.strictEqual(test_object.CheckTypeTag(0, {}), false);
186204
assert.strictEqual(test_object.CheckTypeTag(1, {}), false);
205+
assert.strictEqual(test_object.CheckTypeTag(0, plainExternal), false);
206+
assert.strictEqual(test_object.CheckTypeTag(1, plainExternal), false);
207+
assert.strictEqual(test_object.CheckTypeTag(2, plainExternal), false);
208+
assert.strictEqual(test_object.CheckTypeTag(3, plainExternal), false);
209+
assert.strictEqual(test_object.CheckTypeTag(4, plainExternal), false);
187210
}
188211

189212
{

test/js-native-api/test_object/test_object.c

+77-31
Original file line numberDiff line numberDiff line change
@@ -605,13 +605,23 @@ static napi_value TestSeal(napi_env env,
605605
}
606606

607607
// We create two type tags. They are basically 128-bit UUIDs.
608-
static const napi_type_tag type_tags[5] = {
609-
{ 0xdaf987b3cc62481a, 0xb745b0497f299531 },
610-
{ 0xbb7936c374084d9b, 0xa9548d0762eeedb9 },
611-
{ 0xa5ed9ce2e4c00c38, 0 },
612-
{ 0, 0 },
613-
{ 0xa5ed9ce2e4c00c38, 0xdaf987b3cc62481a },
608+
#define TYPE_TAG_COUNT 5
609+
static const napi_type_tag type_tags[TYPE_TAG_COUNT] = {
610+
{0xdaf987b3cc62481a, 0xb745b0497f299531},
611+
{0xbb7936c374084d9b, 0xa9548d0762eeedb9},
612+
{0xa5ed9ce2e4c00c38, 0},
613+
{0, 0},
614+
{0xa5ed9ce2e4c00c38, 0xdaf987b3cc62481a},
614615
};
616+
#define VALIDATE_TYPE_INDEX(env, type_index) \
617+
do { \
618+
if ((type_index) >= TYPE_TAG_COUNT) { \
619+
NODE_API_CALL((env), \
620+
napi_throw_range_error((env), \
621+
"NODE_API_TEST_INVALID_TYPE_INDEX", \
622+
"Invalid type index")); \
623+
} \
624+
} while (0)
615625

616626
static napi_value
617627
TypeTaggedInstance(napi_env env, napi_callback_info info) {
@@ -621,12 +631,42 @@ TypeTaggedInstance(napi_env env, napi_callback_info info) {
621631

622632
NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, &which_type, NULL, NULL));
623633
NODE_API_CALL(env, napi_get_value_uint32(env, which_type, &type_index));
634+
VALIDATE_TYPE_INDEX(env, type_index);
624635
NODE_API_CALL(env, napi_create_object(env, &instance));
625636
NODE_API_CALL(env, napi_type_tag_object(env, instance, &type_tags[type_index]));
626637

627638
return instance;
628639
}
629640

641+
// V8 will not allowe us to construct an external with a NULL data value.
642+
#define IN_LIEU_OF_NULL ((void*)0x1)
643+
644+
static napi_value PlainExternal(napi_env env, napi_callback_info info) {
645+
napi_value instance;
646+
647+
NODE_API_CALL(
648+
env, napi_create_external(env, IN_LIEU_OF_NULL, NULL, NULL, &instance));
649+
650+
return instance;
651+
}
652+
653+
static napi_value TypeTaggedExternal(napi_env env, napi_callback_info info) {
654+
size_t argc = 1;
655+
uint32_t type_index;
656+
napi_value instance, which_type;
657+
658+
NODE_API_CALL(env,
659+
napi_get_cb_info(env, info, &argc, &which_type, NULL, NULL));
660+
NODE_API_CALL(env, napi_get_value_uint32(env, which_type, &type_index));
661+
VALIDATE_TYPE_INDEX(env, type_index);
662+
NODE_API_CALL(
663+
env, napi_create_external(env, IN_LIEU_OF_NULL, NULL, NULL, &instance));
664+
NODE_API_CALL(env,
665+
napi_type_tag_object(env, instance, &type_tags[type_index]));
666+
667+
return instance;
668+
}
669+
630670
static napi_value
631671
CheckTypeTag(napi_env env, napi_callback_info info) {
632672
size_t argc = 2;
@@ -636,6 +676,7 @@ CheckTypeTag(napi_env env, napi_callback_info info) {
636676

637677
NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, argv, NULL, NULL));
638678
NODE_API_CALL(env, napi_get_value_uint32(env, argv[0], &type_index));
679+
VALIDATE_TYPE_INDEX(env, type_index);
639680
NODE_API_CALL(env, napi_check_object_type_tag(env,
640681
argv[1],
641682
&type_tags[type_index],
@@ -648,31 +689,36 @@ CheckTypeTag(napi_env env, napi_callback_info info) {
648689
EXTERN_C_START
649690
napi_value Init(napi_env env, napi_value exports) {
650691
napi_property_descriptor descriptors[] = {
651-
DECLARE_NODE_API_PROPERTY("Get", Get),
652-
DECLARE_NODE_API_PROPERTY("GetNamed", GetNamed),
653-
DECLARE_NODE_API_PROPERTY("GetPropertyNames", GetPropertyNames),
654-
DECLARE_NODE_API_PROPERTY("GetSymbolNames", GetSymbolNames),
655-
DECLARE_NODE_API_PROPERTY("GetEnumerableWritableNames", GetEnumerableWritableNames),
656-
DECLARE_NODE_API_PROPERTY("GetOwnWritableNames", GetOwnWritableNames),
657-
DECLARE_NODE_API_PROPERTY("GetEnumerableConfigurableNames", GetEnumerableConfigurableNames),
658-
DECLARE_NODE_API_PROPERTY("GetOwnConfigurableNames", GetOwnConfigurableNames),
659-
DECLARE_NODE_API_PROPERTY("Set", Set),
660-
DECLARE_NODE_API_PROPERTY("SetNamed", SetNamed),
661-
DECLARE_NODE_API_PROPERTY("Has", Has),
662-
DECLARE_NODE_API_PROPERTY("HasNamed", HasNamed),
663-
DECLARE_NODE_API_PROPERTY("HasOwn", HasOwn),
664-
DECLARE_NODE_API_PROPERTY("Delete", Delete),
665-
DECLARE_NODE_API_PROPERTY("New", New),
666-
DECLARE_NODE_API_PROPERTY("Inflate", Inflate),
667-
DECLARE_NODE_API_PROPERTY("Wrap", Wrap),
668-
DECLARE_NODE_API_PROPERTY("Unwrap", Unwrap),
669-
DECLARE_NODE_API_PROPERTY("TestSetProperty", TestSetProperty),
670-
DECLARE_NODE_API_PROPERTY("TestHasProperty", TestHasProperty),
671-
DECLARE_NODE_API_PROPERTY("TypeTaggedInstance", TypeTaggedInstance),
672-
DECLARE_NODE_API_PROPERTY("CheckTypeTag", CheckTypeTag),
673-
DECLARE_NODE_API_PROPERTY("TestGetProperty", TestGetProperty),
674-
DECLARE_NODE_API_PROPERTY("TestFreeze", TestFreeze),
675-
DECLARE_NODE_API_PROPERTY("TestSeal", TestSeal),
692+
DECLARE_NODE_API_PROPERTY("Get", Get),
693+
DECLARE_NODE_API_PROPERTY("GetNamed", GetNamed),
694+
DECLARE_NODE_API_PROPERTY("GetPropertyNames", GetPropertyNames),
695+
DECLARE_NODE_API_PROPERTY("GetSymbolNames", GetSymbolNames),
696+
DECLARE_NODE_API_PROPERTY("GetEnumerableWritableNames",
697+
GetEnumerableWritableNames),
698+
DECLARE_NODE_API_PROPERTY("GetOwnWritableNames", GetOwnWritableNames),
699+
DECLARE_NODE_API_PROPERTY("GetEnumerableConfigurableNames",
700+
GetEnumerableConfigurableNames),
701+
DECLARE_NODE_API_PROPERTY("GetOwnConfigurableNames",
702+
GetOwnConfigurableNames),
703+
DECLARE_NODE_API_PROPERTY("Set", Set),
704+
DECLARE_NODE_API_PROPERTY("SetNamed", SetNamed),
705+
DECLARE_NODE_API_PROPERTY("Has", Has),
706+
DECLARE_NODE_API_PROPERTY("HasNamed", HasNamed),
707+
DECLARE_NODE_API_PROPERTY("HasOwn", HasOwn),
708+
DECLARE_NODE_API_PROPERTY("Delete", Delete),
709+
DECLARE_NODE_API_PROPERTY("New", New),
710+
DECLARE_NODE_API_PROPERTY("Inflate", Inflate),
711+
DECLARE_NODE_API_PROPERTY("Wrap", Wrap),
712+
DECLARE_NODE_API_PROPERTY("Unwrap", Unwrap),
713+
DECLARE_NODE_API_PROPERTY("TestSetProperty", TestSetProperty),
714+
DECLARE_NODE_API_PROPERTY("TestHasProperty", TestHasProperty),
715+
DECLARE_NODE_API_PROPERTY("TypeTaggedInstance", TypeTaggedInstance),
716+
DECLARE_NODE_API_PROPERTY("TypeTaggedExternal", TypeTaggedExternal),
717+
DECLARE_NODE_API_PROPERTY("PlainExternal", PlainExternal),
718+
DECLARE_NODE_API_PROPERTY("CheckTypeTag", CheckTypeTag),
719+
DECLARE_NODE_API_PROPERTY("TestGetProperty", TestGetProperty),
720+
DECLARE_NODE_API_PROPERTY("TestFreeze", TestFreeze),
721+
DECLARE_NODE_API_PROPERTY("TestSeal", TestSeal),
676722
};
677723

678724
init_test_null(env, exports);

0 commit comments

Comments
 (0)