Skip to content

Commit b11e4de

Browse files
KevinEadymhdawson
authored andcommitted
src: add Object::TypeTag, Object::CheckTypeTag
PR-URL: #1261 Reviewed-By: Michael Dawson <[email protected]
1 parent 34221c1 commit b11e4de

File tree

9 files changed

+148
-0
lines changed

9 files changed

+148
-0
lines changed

doc/external.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,13 @@ The `Napi::External` template class implements the ability to create a `Napi::Va
66

77
`Napi::External` objects can be created with an optional Finalizer function and optional Hint value. The Finalizer function, if specified, is called when your `Napi::External` object is released by Node's garbage collector. It gives your code the opportunity to free any dynamically created data. If you specify a Hint value, it is passed to your Finalizer function.
88

9+
Note that `Napi::Value::IsExternal()` will return `true` for any external value.
10+
It does not differentiate between the templated parameter `T` in
11+
`Napi::External<T>`. It is up to the addon to ensure an `Napi::External<T>`
12+
object holds the correct `T` when retrieving the data via
13+
`Napi::External<T>::Data()`. One method to ensure an object is of a specific
14+
type is through [type tags](./object.md#TypeTag).
15+
916
## Methods
1017

1118
### New

doc/object.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,33 @@ from being added to it and marking all existing properties as non-configurable.
241241
Values of present properties can still be changed as long as they are
242242
writable.
243243

244+
### TypeTag()
245+
246+
```cpp
247+
void Napi::Object::TypeTag(const napi_type_tag* type_tag) const;
248+
```
249+
250+
- `[in] type_tag`: The tag with which this object is to be marked.
251+
252+
The `Napi::Object::TypeTag()` method associates the value of the `type_tag`
253+
pointer with this JavaScript object. `Napi::Object::CheckTypeTag()` can then be
254+
used to compare the tag that was attached to this object with one owned by the
255+
addon to ensure that this object has the right type.
256+
257+
### CheckTypeTag()
258+
259+
```cpp
260+
bool Napi::Object::CheckTypeTag(const napi_type_tag* type_tag) const;
261+
```
262+
263+
- `[in] type_tag`: The tag with which to compare any tag found on this object.
264+
265+
The `Napi::Object::CheckTypeTag()` method compares the pointer given as
266+
`type_tag` with any that can be found on this JavaScript object. If no tag is
267+
found on this object or, if a tag is found but it does not match `type_tag`,
268+
then the return value is `false`. If a tag is found and it matches `type_tag`,
269+
then the return value is `true`.
270+
244271
### operator\[\]()
245272

246273
```cpp

napi-inl.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1626,6 +1626,19 @@ inline MaybeOrValue<bool> Object::Seal() const {
16261626
napi_status status = napi_object_seal(_env, _value);
16271627
NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool);
16281628
}
1629+
1630+
inline void Object::TypeTag(const napi_type_tag* type_tag) const {
1631+
napi_status status = napi_type_tag_object(_env, _value, type_tag);
1632+
NAPI_THROW_IF_FAILED_VOID(_env, status);
1633+
}
1634+
1635+
inline bool Object::CheckTypeTag(const napi_type_tag* type_tag) const {
1636+
bool result;
1637+
napi_status status =
1638+
napi_check_object_type_tag(_env, _value, type_tag, &result);
1639+
NAPI_THROW_IF_FAILED(_env, status, false);
1640+
return result;
1641+
}
16291642
#endif // NAPI_VERSION >= 8
16301643

16311644
////////////////////////////////////////////////////////////////////////////////

napi.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -980,6 +980,9 @@ class Object : public Value {
980980
/// See
981981
/// https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-getprototypeof
982982
MaybeOrValue<bool> Seal() const;
983+
984+
void TypeTag(const napi_type_tag* type_tag) const;
985+
bool CheckTypeTag(const napi_type_tag* type_tag) const;
983986
#endif // NAPI_VERSION >= 8
984987
};
985988

test/binding.cc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ Object InitVersionManagement(Env env);
7575
Object InitThunkingManual(Env env);
7676
#if (NAPI_VERSION > 7)
7777
Object InitObjectFreezeSeal(Env env);
78+
Object InitObjectTypeTag(Env env);
7879
#endif
7980

8081
#if defined(NODE_ADDON_API_ENABLE_MAYBE)
@@ -166,6 +167,7 @@ Object Init(Env env, Object exports) {
166167
exports.Set("thunking_manual", InitThunkingManual(env));
167168
#if (NAPI_VERSION > 7)
168169
exports.Set("object_freeze_seal", InitObjectFreezeSeal(env));
170+
exports.Set("object_type_tag", InitObjectTypeTag(env));
169171
#endif
170172

171173
#if defined(NODE_ADDON_API_ENABLE_MAYBE)

test/binding.gyp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
'object/has_property.cc',
4848
'object/object.cc',
4949
'object/object_freeze_seal.cc',
50+
'object/object_type_tag.cc',
5051
'object/set_property.cc',
5152
'object/subscript_operator.cc',
5253
'promise.cc',

test/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ if (majorNodeVersion < 12 && !filterConditionsProvided) {
134134

135135
if (napiVersion < 8 && !filterConditionsProvided) {
136136
testModules.splice(testModules.indexOf('object/object_freeze_seal'), 1);
137+
testModules.splice(testModules.indexOf('object/object_type_tag'), 1);
137138
}
138139

139140
(async function () {

test/object/object_type_tag.cc

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
#include "napi.h"
2+
3+
#if (NAPI_VERSION > 7)
4+
5+
using namespace Napi;
6+
7+
static const napi_type_tag type_tags[5] = {
8+
{0xdaf987b3cc62481a, 0xb745b0497f299531},
9+
{0xbb7936c374084d9b, 0xa9548d0762eeedb9},
10+
{0xa5ed9ce2e4c00c38, 0},
11+
{0, 0},
12+
{0xa5ed9ce2e4c00c38, 0xdaf987b3cc62481a},
13+
};
14+
15+
Value TypeTaggedInstance(const CallbackInfo& info) {
16+
Object instance = Object::New(info.Env());
17+
uint32_t type_index = info[0].As<Number>().Int32Value();
18+
19+
instance.TypeTag(&type_tags[type_index]);
20+
21+
return instance;
22+
}
23+
24+
Value CheckTypeTag(const CallbackInfo& info) {
25+
uint32_t type_index = info[0].As<Number>().Int32Value();
26+
Object instance = info[1].As<Object>();
27+
28+
return Boolean::New(info.Env(),
29+
instance.CheckTypeTag(&type_tags[type_index]));
30+
}
31+
32+
Object InitObjectTypeTag(Env env) {
33+
Object exports = Object::New(env);
34+
exports["checkTypeTag"] = Function::New(env, CheckTypeTag);
35+
exports["typedTaggedInstance"] = Function::New(env, TypeTaggedInstance);
36+
return exports;
37+
}
38+
39+
#endif

test/object/object_type_tag.js

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
'use strict';
2+
3+
const assert = require('assert');
4+
5+
module.exports = require('../common').runTest(test);
6+
7+
// eslint-disable-next-line camelcase
8+
function test ({ object_type_tag }) {
9+
const obj1 = object_type_tag.typedTaggedInstance(0);
10+
const obj2 = object_type_tag.typedTaggedInstance(1);
11+
12+
// Verify that type tags are correctly accepted.
13+
assert.strictEqual(object_type_tag.checkTypeTag(0, obj1), true);
14+
assert.strictEqual(object_type_tag.checkTypeTag(1, obj2), true);
15+
16+
// Verify that wrongly tagged objects are rejected.
17+
assert.strictEqual(object_type_tag.checkTypeTag(0, obj2), false);
18+
assert.strictEqual(object_type_tag.checkTypeTag(1, obj1), false);
19+
20+
// Verify that untagged objects are rejected.
21+
assert.strictEqual(object_type_tag.checkTypeTag(0, {}), false);
22+
assert.strictEqual(object_type_tag.checkTypeTag(1, {}), false);
23+
24+
// Node v14 and v16 have an issue checking type tags if the `upper` in
25+
// `napi_type_tag` is 0, so these tests can only be performed on Node version
26+
// >=18. See:
27+
// - https://github.com/nodejs/node/issues/43786
28+
// - https://github.com/nodejs/node/pull/43788
29+
const nodeVersion = parseInt(process.versions.node.split('.')[0]);
30+
if (nodeVersion < 18) {
31+
return;
32+
}
33+
34+
const obj3 = object_type_tag.typedTaggedInstance(2);
35+
const obj4 = object_type_tag.typedTaggedInstance(3);
36+
37+
// Verify that untagged objects are rejected.
38+
assert.strictEqual(object_type_tag.checkTypeTag(0, {}), false);
39+
assert.strictEqual(object_type_tag.checkTypeTag(1, {}), false);
40+
41+
// Verify that type tags are correctly accepted.
42+
assert.strictEqual(object_type_tag.checkTypeTag(0, obj1), true);
43+
assert.strictEqual(object_type_tag.checkTypeTag(1, obj2), true);
44+
assert.strictEqual(object_type_tag.checkTypeTag(2, obj3), true);
45+
assert.strictEqual(object_type_tag.checkTypeTag(3, obj4), true);
46+
47+
// Verify that wrongly tagged objects are rejected.
48+
assert.strictEqual(object_type_tag.checkTypeTag(0, obj2), false);
49+
assert.strictEqual(object_type_tag.checkTypeTag(1, obj1), false);
50+
assert.strictEqual(object_type_tag.checkTypeTag(0, obj3), false);
51+
assert.strictEqual(object_type_tag.checkTypeTag(1, obj4), false);
52+
assert.strictEqual(object_type_tag.checkTypeTag(2, obj4), false);
53+
assert.strictEqual(object_type_tag.checkTypeTag(3, obj3), false);
54+
assert.strictEqual(object_type_tag.checkTypeTag(4, obj3), false);
55+
}

0 commit comments

Comments
 (0)