Skip to content

Commit

Permalink
src: add Object::TypeTag, Object::CheckTypeTag
Browse files Browse the repository at this point in the history
PR-URL: #1261
Reviewed-By: Michael Dawson <midawson@redhat.com
  • Loading branch information
KevinEady authored and mhdawson committed Jan 13, 2023
1 parent 34221c1 commit b11e4de
Show file tree
Hide file tree
Showing 9 changed files with 148 additions and 0 deletions.
7 changes: 7 additions & 0 deletions doc/external.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@ The `Napi::External` template class implements the ability to create a `Napi::Va

`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.

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

## Methods

### New
Expand Down
27 changes: 27 additions & 0 deletions doc/object.md
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,33 @@ from being added to it and marking all existing properties as non-configurable.
Values of present properties can still be changed as long as they are
writable.

### TypeTag()

```cpp
void Napi::Object::TypeTag(const napi_type_tag* type_tag) const;
```
- `[in] type_tag`: The tag with which this object is to be marked.
The `Napi::Object::TypeTag()` method associates the value of the `type_tag`
pointer with this JavaScript object. `Napi::Object::CheckTypeTag()` can then be
used to compare the tag that was attached to this object with one owned by the
addon to ensure that this object has the right type.
### CheckTypeTag()
```cpp
bool Napi::Object::CheckTypeTag(const napi_type_tag* type_tag) const;
```

- `[in] type_tag`: The tag with which to compare any tag found on this object.

The `Napi::Object::CheckTypeTag()` method compares the pointer given as
`type_tag` with any that can be found on this JavaScript object. If no tag is
found on this object or, if a tag is found but it does not match `type_tag`,
then the return value is `false`. If a tag is found and it matches `type_tag`,
then the return value is `true`.

### operator\[\]()

```cpp
Expand Down
13 changes: 13 additions & 0 deletions napi-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -1626,6 +1626,19 @@ inline MaybeOrValue<bool> Object::Seal() const {
napi_status status = napi_object_seal(_env, _value);
NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool);
}

inline void Object::TypeTag(const napi_type_tag* type_tag) const {
napi_status status = napi_type_tag_object(_env, _value, type_tag);
NAPI_THROW_IF_FAILED_VOID(_env, status);
}

inline bool Object::CheckTypeTag(const napi_type_tag* type_tag) const {
bool result;
napi_status status =
napi_check_object_type_tag(_env, _value, type_tag, &result);
NAPI_THROW_IF_FAILED(_env, status, false);
return result;
}
#endif // NAPI_VERSION >= 8

////////////////////////////////////////////////////////////////////////////////
Expand Down
3 changes: 3 additions & 0 deletions napi.h
Original file line number Diff line number Diff line change
Expand Up @@ -980,6 +980,9 @@ class Object : public Value {
/// See
/// https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-getprototypeof
MaybeOrValue<bool> Seal() const;

void TypeTag(const napi_type_tag* type_tag) const;
bool CheckTypeTag(const napi_type_tag* type_tag) const;
#endif // NAPI_VERSION >= 8
};

Expand Down
2 changes: 2 additions & 0 deletions test/binding.cc
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ Object InitVersionManagement(Env env);
Object InitThunkingManual(Env env);
#if (NAPI_VERSION > 7)
Object InitObjectFreezeSeal(Env env);
Object InitObjectTypeTag(Env env);
#endif

#if defined(NODE_ADDON_API_ENABLE_MAYBE)
Expand Down Expand Up @@ -166,6 +167,7 @@ Object Init(Env env, Object exports) {
exports.Set("thunking_manual", InitThunkingManual(env));
#if (NAPI_VERSION > 7)
exports.Set("object_freeze_seal", InitObjectFreezeSeal(env));
exports.Set("object_type_tag", InitObjectTypeTag(env));
#endif

#if defined(NODE_ADDON_API_ENABLE_MAYBE)
Expand Down
1 change: 1 addition & 0 deletions test/binding.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
'object/has_property.cc',
'object/object.cc',
'object/object_freeze_seal.cc',
'object/object_type_tag.cc',
'object/set_property.cc',
'object/subscript_operator.cc',
'promise.cc',
Expand Down
1 change: 1 addition & 0 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ if (majorNodeVersion < 12 && !filterConditionsProvided) {

if (napiVersion < 8 && !filterConditionsProvided) {
testModules.splice(testModules.indexOf('object/object_freeze_seal'), 1);
testModules.splice(testModules.indexOf('object/object_type_tag'), 1);
}

(async function () {
Expand Down
39 changes: 39 additions & 0 deletions test/object/object_type_tag.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#include "napi.h"

#if (NAPI_VERSION > 7)

using namespace Napi;

static const napi_type_tag type_tags[5] = {
{0xdaf987b3cc62481a, 0xb745b0497f299531},
{0xbb7936c374084d9b, 0xa9548d0762eeedb9},
{0xa5ed9ce2e4c00c38, 0},
{0, 0},
{0xa5ed9ce2e4c00c38, 0xdaf987b3cc62481a},
};

Value TypeTaggedInstance(const CallbackInfo& info) {
Object instance = Object::New(info.Env());
uint32_t type_index = info[0].As<Number>().Int32Value();

instance.TypeTag(&type_tags[type_index]);

return instance;
}

Value CheckTypeTag(const CallbackInfo& info) {
uint32_t type_index = info[0].As<Number>().Int32Value();
Object instance = info[1].As<Object>();

return Boolean::New(info.Env(),
instance.CheckTypeTag(&type_tags[type_index]));
}

Object InitObjectTypeTag(Env env) {
Object exports = Object::New(env);
exports["checkTypeTag"] = Function::New(env, CheckTypeTag);
exports["typedTaggedInstance"] = Function::New(env, TypeTaggedInstance);
return exports;
}

#endif
55 changes: 55 additions & 0 deletions test/object/object_type_tag.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
'use strict';

const assert = require('assert');

module.exports = require('../common').runTest(test);

// eslint-disable-next-line camelcase
function test ({ object_type_tag }) {
const obj1 = object_type_tag.typedTaggedInstance(0);
const obj2 = object_type_tag.typedTaggedInstance(1);

// Verify that type tags are correctly accepted.
assert.strictEqual(object_type_tag.checkTypeTag(0, obj1), true);
assert.strictEqual(object_type_tag.checkTypeTag(1, obj2), true);

// Verify that wrongly tagged objects are rejected.
assert.strictEqual(object_type_tag.checkTypeTag(0, obj2), false);
assert.strictEqual(object_type_tag.checkTypeTag(1, obj1), false);

// Verify that untagged objects are rejected.
assert.strictEqual(object_type_tag.checkTypeTag(0, {}), false);
assert.strictEqual(object_type_tag.checkTypeTag(1, {}), false);

// Node v14 and v16 have an issue checking type tags if the `upper` in
// `napi_type_tag` is 0, so these tests can only be performed on Node version
// >=18. See:
// - https://github.com/nodejs/node/issues/43786
// - https://github.com/nodejs/node/pull/43788
const nodeVersion = parseInt(process.versions.node.split('.')[0]);
if (nodeVersion < 18) {
return;
}

const obj3 = object_type_tag.typedTaggedInstance(2);
const obj4 = object_type_tag.typedTaggedInstance(3);

// Verify that untagged objects are rejected.
assert.strictEqual(object_type_tag.checkTypeTag(0, {}), false);
assert.strictEqual(object_type_tag.checkTypeTag(1, {}), false);

// Verify that type tags are correctly accepted.
assert.strictEqual(object_type_tag.checkTypeTag(0, obj1), true);
assert.strictEqual(object_type_tag.checkTypeTag(1, obj2), true);
assert.strictEqual(object_type_tag.checkTypeTag(2, obj3), true);
assert.strictEqual(object_type_tag.checkTypeTag(3, obj4), true);

// Verify that wrongly tagged objects are rejected.
assert.strictEqual(object_type_tag.checkTypeTag(0, obj2), false);
assert.strictEqual(object_type_tag.checkTypeTag(1, obj1), false);
assert.strictEqual(object_type_tag.checkTypeTag(0, obj3), false);
assert.strictEqual(object_type_tag.checkTypeTag(1, obj4), false);
assert.strictEqual(object_type_tag.checkTypeTag(2, obj4), false);
assert.strictEqual(object_type_tag.checkTypeTag(3, obj3), false);
assert.strictEqual(object_type_tag.checkTypeTag(4, obj3), false);
}

0 comments on commit b11e4de

Please sign in to comment.