Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nodejs/node.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--benchmark/napi/type-tag-check/binding.gyp8
-rw-r--r--benchmark/napi/type-tag-check/index.js18
-rw-r--r--benchmark/napi/type-tag/binding.c84
-rw-r--r--benchmark/napi/type-tag/binding.gyp8
-rw-r--r--benchmark/napi/type-tag/check-object-tag.js18
-rw-r--r--benchmark/napi/type-tag/index.js18
-rw-r--r--doc/api/n-api.md213
-rw-r--r--src/env.h1
-rw-r--r--src/js_native_api.h10
-rw-r--r--src/js_native_api_types.h7
-rw-r--r--src/js_native_api_v8.cc69
-rw-r--r--src/js_native_api_v8.h32
-rw-r--r--test/js-native-api/test_object/test.js18
-rw-r--r--test/js-native-api/test_object/test_object.c41
14 files changed, 545 insertions, 0 deletions
diff --git a/benchmark/napi/type-tag-check/binding.gyp b/benchmark/napi/type-tag-check/binding.gyp
new file mode 100644
index 00000000000..595ab325233
--- /dev/null
+++ b/benchmark/napi/type-tag-check/binding.gyp
@@ -0,0 +1,8 @@
+{
+ 'targets': [
+ {
+ 'target_name': 'binding',
+ 'sources': [ '../type-tag/binding.c' ]
+ }
+ ]
+}
diff --git a/benchmark/napi/type-tag-check/index.js b/benchmark/napi/type-tag-check/index.js
new file mode 100644
index 00000000000..346dfb7812d
--- /dev/null
+++ b/benchmark/napi/type-tag-check/index.js
@@ -0,0 +1,18 @@
+'use strict';
+const common = require('../../common.js');
+
+let binding;
+try {
+ binding = require(`./build/${common.buildType}/binding`);
+} catch {
+ console.error(`${__filename}: Binding failed to load`);
+ process.exit(0);
+}
+
+const bench = common.createBenchmark(main, {
+ n: [1e5, 1e6, 1e7],
+});
+
+function main({ n }) {
+ binding.checkObjectTag(n, bench, bench.start, bench.end);
+}
diff --git a/benchmark/napi/type-tag/binding.c b/benchmark/napi/type-tag/binding.c
new file mode 100644
index 00000000000..7bc8b5d7502
--- /dev/null
+++ b/benchmark/napi/type-tag/binding.c
@@ -0,0 +1,84 @@
+#include <assert.h>
+#define NAPI_EXPERIMENTAL
+#include <node_api.h>
+
+#define NAPI_CALL(call) \
+ do { \
+ napi_status status = call; \
+ assert(status == napi_ok && #call " failed"); \
+ } while (0);
+
+#define EXPORT_FUNC(env, exports, name, func) \
+ do { \
+ napi_value js_func; \
+ NAPI_CALL(napi_create_function((env), \
+ (name), \
+ NAPI_AUTO_LENGTH, \
+ (func), \
+ NULL, \
+ &js_func)); \
+ NAPI_CALL(napi_set_named_property((env), \
+ (exports), \
+ (name), \
+ js_func)); \
+ } while (0);
+
+static const napi_type_tag tag = {
+ 0xe7ecbcd5954842f6, 0x9e75161c9bf27282
+};
+
+static napi_value TagObject(napi_env env, napi_callback_info info) {
+ size_t argc = 4;
+ napi_value argv[4];
+ uint32_t n;
+ uint32_t index;
+ napi_handle_scope scope;
+
+ NAPI_CALL(napi_get_cb_info(env, info, &argc, argv, NULL, NULL));
+ NAPI_CALL(napi_get_value_uint32(env, argv[0], &n));
+ NAPI_CALL(napi_open_handle_scope(env, &scope));
+ napi_value objects[n];
+ for (index = 0; index < n; index++) {
+ NAPI_CALL(napi_create_object(env, &objects[index]));
+ }
+
+ // Time the object tag creation.
+ NAPI_CALL(napi_call_function(env, argv[1], argv[2], 0, NULL, NULL));
+ for (index = 0; index < n; index++) {
+ NAPI_CALL(napi_type_tag_object(env, objects[index], &tag));
+ }
+ NAPI_CALL(napi_call_function(env, argv[1], argv[3], 1, &argv[0], NULL));
+
+ NAPI_CALL(napi_close_handle_scope(env, scope));
+ return NULL;
+}
+
+static napi_value CheckObjectTag(napi_env env, napi_callback_info info) {
+ size_t argc = 4;
+ napi_value argv[4];
+ uint32_t n;
+ uint32_t index;
+ bool is_of_type;
+
+ NAPI_CALL(napi_get_cb_info(env, info, &argc, argv, NULL, NULL));
+ NAPI_CALL(napi_get_value_uint32(env, argv[0], &n));
+ napi_value object;
+ NAPI_CALL(napi_create_object(env, &object));
+ NAPI_CALL(napi_type_tag_object(env, object, &tag));
+
+ // Time the object tag checking.
+ NAPI_CALL(napi_call_function(env, argv[1], argv[2], 0, NULL, NULL));
+ for (index = 0; index < n; index++) {
+ NAPI_CALL(napi_check_object_type_tag(env, object, &tag, &is_of_type));
+ assert(is_of_type && " type mismatch");
+ }
+ NAPI_CALL(napi_call_function(env, argv[1], argv[3], 1, &argv[0], NULL));
+
+ return NULL;
+}
+
+NAPI_MODULE_INIT() {
+ EXPORT_FUNC(env, exports, "tagObject", TagObject);
+ EXPORT_FUNC(env, exports, "checkObjectTag", CheckObjectTag);
+ return exports;
+}
diff --git a/benchmark/napi/type-tag/binding.gyp b/benchmark/napi/type-tag/binding.gyp
new file mode 100644
index 00000000000..413621ade33
--- /dev/null
+++ b/benchmark/napi/type-tag/binding.gyp
@@ -0,0 +1,8 @@
+{
+ 'targets': [
+ {
+ 'target_name': 'binding',
+ 'sources': [ 'binding.c' ]
+ }
+ ]
+}
diff --git a/benchmark/napi/type-tag/check-object-tag.js b/benchmark/napi/type-tag/check-object-tag.js
new file mode 100644
index 00000000000..346dfb7812d
--- /dev/null
+++ b/benchmark/napi/type-tag/check-object-tag.js
@@ -0,0 +1,18 @@
+'use strict';
+const common = require('../../common.js');
+
+let binding;
+try {
+ binding = require(`./build/${common.buildType}/binding`);
+} catch {
+ console.error(`${__filename}: Binding failed to load`);
+ process.exit(0);
+}
+
+const bench = common.createBenchmark(main, {
+ n: [1e5, 1e6, 1e7],
+});
+
+function main({ n }) {
+ binding.checkObjectTag(n, bench, bench.start, bench.end);
+}
diff --git a/benchmark/napi/type-tag/index.js b/benchmark/napi/type-tag/index.js
new file mode 100644
index 00000000000..3f85b9af8e7
--- /dev/null
+++ b/benchmark/napi/type-tag/index.js
@@ -0,0 +1,18 @@
+'use strict';
+const common = require('../../common.js');
+
+let binding;
+try {
+ binding = require(`./build/${common.buildType}/binding`);
+} catch {
+ console.error(`${__filename}: Binding failed to load`);
+ process.exit(0);
+}
+
+const bench = common.createBenchmark(main, {
+ n: [1e3, 1e4, 1e5],
+});
+
+function main({ n }) {
+ binding.tagObject(n, bench, bench.start, bench.end);
+}
diff --git a/doc/api/n-api.md b/doc/api/n-api.md
index f45dd2dd4c6..8dff1e0e15f 100644
--- a/doc/api/n-api.md
+++ b/doc/api/n-api.md
@@ -602,6 +602,27 @@ minimum lifetimes explicitly.
For more details, review the [Object lifetime management][].
+#### napi_type_tag
+<!-- YAML
+added: REPLACEME
+-->
+
+A 128-bit value stored as two unsigned 64-bit integers. It serves as a UUID
+with which JavaScript objects can be "tagged" in order to ensure that they are
+of a certain type. This is a stronger check than [`napi_instanceof`][], because
+the latter can report a false positive if the object's prototype has been
+manipulated. Type-tagging is most useful in conjunction with [`napi_wrap`][]
+because it ensures that the pointer retrieved from a wrapped object can be
+safely cast to the native type corresponding to the type tag that had been
+previously applied to the JavaScript object.
+
+```c
+typedef struct {
+ uint64_t lower;
+ uint64_t upper;
+} napi_type_tag;
+```
+
### N-API callback types
#### napi_callback_info
@@ -4288,6 +4309,143 @@ if (is_instance) {
The reference must be freed once it is no longer needed.
+There are occasions where `napi_instanceof()` is insufficient for ensuring that
+a JavaScript object is a wrapper for a certain native type. This is the case
+especially when wrapped JavaScript objects are passed back into the addon via
+static methods rather than as the `this` value of prototype methods. In such
+cases there is a chance that they may be unwrapped incorrectly.
+
+```js
+const myAddon = require('./build/Release/my_addon.node');
+
+// `openDatabase()` returns a JavaScript object that wraps a native database
+// handle.
+const dbHandle = myAddon.openDatabase();
+
+// `query()` returns a JavaScript object that wraps a native query handle.
+const queryHandle = myAddon.query(dbHandle, 'Gimme ALL the things!');
+
+// There is an accidental error in the line below. The first parameter to
+// `myAddon.queryHasRecords()` should be the database handle (`dbHandle`), not
+// the query handle (`query`), so the correct condition for the while-loop
+// should be
+//
+// myAddon.queryHasRecords(dbHandle, queryHandle)
+//
+while (myAddon.queryHasRecords(queryHandle, dbHandle)) {
+ // retrieve records
+}
+```
+
+In the above example `myAddon.queryHasRecords()` is a method that accepts two
+arguments. The first is a database handle and the second is a query handle.
+Internally, it unwraps the first argument and casts the resulting pointer to a
+native database handle. It then unwraps the second argument and casts the
+resulting pointer to a query handle. If the arguments are passed in the wrong
+order, the casts will work, however, there is a good chance that the underlying
+database operation will fail, or will even cause an invalid memory access.
+
+To ensure that the pointer retrieved from the first argument is indeed a pointer
+to a database handle and, similarly, that the pointer retrieved from the second
+argument is indeed a pointer to a query handle, the implementation of
+`queryHasRecords()` has to perform a type validation. Retaining the JavaScript
+class constructor from which the database handle was instantiated and the
+constructor from which the query handle was instantiated in `napi_ref`s can
+help, because `napi_instanceof()` can then be used to ensure that the instances
+passed into `queryHashRecords()` are indeed of the correct type.
+
+Unfortunately, `napi_instanceof()` does not protect against prototype
+manipulation. For example, the prototype of the database handle instance can be
+set to the prototype of the constructor for query handle instances. In this
+case, the database handle instance can appear as a query handle instance, and it
+will pass the `napi_instanceof()` test for a query handle instance, while still
+containing a pointer to a database handle.
+
+To this end, N-API provides type-tagging capabilities.
+
+A type tag is a 128-bit integer unique to the addon. N-API provides the
+`napi_type_tag` structure for storing a type tag. When such a value is passed
+along with a JavaScript object stored in a `napi_value` to
+`napi_type_tag_object()`, the JavaScript object will be "marked" with the
+type tag. The "mark" is invisible on the JavaScript side. When a JavaScript
+object arrives into a native binding, `napi_check_object_type_tag()` can be used
+along with the original type tag to determine whether the JavaScript object was
+previously "marked" with the type tag. This creates a type-checking capability
+of a higher fidelity than `napi_instanceof()` can provide, because such type-
+tagging survives prototype manipulation and addon unloading/reloading.
+
+Continuing the above example, the following skeleton addon implementation
+illustrates the use of `napi_type_tag_object()` and
+`napi_check_object_type_tag()`.
+
+```c
+// This value is the type tag for a database handle. The command
+//
+// uuidgen | sed -r -e 's/-//g' -e 's/(.{16})(.*)/0x\1, 0x\2/'
+//
+// can be used to obtain the two values with which to initialize the structure.
+static const napi_type_tag DatabaseHandleTypeTag = {
+ 0x1edf75a38336451d, 0xa5ed9ce2e4c00c38
+};
+
+// This value is the type tag for a query handle.
+static const napi_type_tag QueryHandleTypeTag = {
+ 0x9c73317f9fad44a3, 0x93c3920bf3b0ad6a
+};
+
+static napi_value
+openDatabase(napi_env env, napi_callback_info info) {
+ napi_status status;
+ napi_value result;
+
+ // Perform the underlying action which results in a database handle.
+ DatabaseHandle* dbHandle = open_database();
+
+ // Create a new, empty JS object.
+ status = napi_create_object(env, &result);
+ if (status != napi_ok) return NULL;
+
+ // Tag the object to indicate that it holds a pointer to a `DatabaseHandle`.
+ status = napi_type_tag_object(env, result, &DatabaseHandleTypeTag);
+ if (status != napi_ok) return NULL;
+
+ // Store the pointer to the `DatabaseHandle` structure inside the JS object.
+ status = napi_wrap(env, result, dbHandle, NULL, NULL, NULL);
+ if (status != napi_ok) return NULL;
+
+ return result;
+}
+
+// Later when we receive a JavaScript object purporting to be a database handle
+// we can use `napi_check_object_type_tag()` to ensure that it is indeed such a
+// handle.
+
+static napi_value
+query(napi_env env, napi_callback_info info) {
+ napi_status status;
+ size_t argc = 2;
+ napi_value argv[2];
+ bool is_db_handle;
+
+ status = napi_get_cb_info(env, info, &argc, argv, NULL, NULL);
+ if (status != napi_ok) return NULL;
+
+ // Check that the object passed as the first parameter has the previously
+ // applied tag.
+ status = napi_check_object_type_tag(env,
+ argv[0],
+ &DatabaseHandleTypeTag,
+ &is_db_handle);
+ if (status != napi_ok) return NULL;
+
+ // Throw a `TypeError` if it doesn't.
+ if (!is_db_handle) {
+ // Throw a TypeError.
+ return NULL;
+ }
+}
+```
+
### napi_define_class
<!-- YAML
added: v8.0.0
@@ -4461,6 +4619,60 @@ object `js_object` using `napi_wrap()` and removes the wrapping. If a finalize
callback was associated with the wrapping, it will no longer be called when the
JavaScript object becomes garbage-collected.
+### napi_type_tag_object
+<!-- YAML
+added: REPLACEME
+-->
+
+> Stability: 1 - Experimental
+
+```c
+napi_status napi_type_tag_object(napi_env env,
+ napi_value js_object,
+ const napi_type_tag* type_tag);
+```
+
+* `[in] env`: The environment that the API is invoked under.
+* `[in] js_object`: The JavaScript object to be marked.
+* `[in] type_tag`: The tag with which the object is to be marked.
+
+Returns `napi_ok` if the API succeeded.
+
+Associates the value of the `type_tag` pointer with the JavaScript object.
+`napi_check_object_type_tag()` can then be used to compare the tag that was
+attached to the object with one owned by the addon to ensure that the object
+has the right type.
+
+If the object already has an associated type tag, this API will return
+`napi_invalid_arg`.
+
+### napi_check_object_type_tag
+<!-- YAML
+added: REPLACEME
+-->
+
+> Stability: 1 - Experimental
+
+```c
+napi_status napi_check_object_type_tag(napi_env env,
+ napi_value js_object,
+ const napi_type_tag* type_tag,
+ bool* result);
+```
+
+* `[in] env`: The environment that the API is invoked under.
+* `[in] js_object`: The JavaScript object whose type tag to examine.
+* `[in] type_tag`: The tag with which to compare any tag found on the object.
+* `[out] result`: Whether the type tag given matched the type tag on the
+object. `false` is also returned if no type tag was found on the object.
+
+Returns `napi_ok` if the API succeeded.
+
+Compares the pointer given as `type_tag` with any that can be found on
+`js_object`. If no tag is found on `js_object` or, if a tag is found but it does
+not match `type_tag`, then `result` is set to `false`. If a tag is found and it
+matches `type_tag`, then `result` is set to `true`.
+
### napi_add_finalizer
<!-- YAML
@@ -5523,6 +5735,7 @@ This API may only be called from the main thread.
[`napi_get_reference_value`]: #n_api_napi_get_reference_value
[`napi_get_value_external`]: #n_api_napi_get_value_external
[`napi_has_property`]: #n_api_napi_has_property
+[`napi_instanceof`]: #n_api_napi_instanceof
[`napi_is_error`]: #n_api_napi_is_error
[`napi_is_exception_pending`]: #n_api_napi_is_exception_pending
[`napi_make_callback`]: #n_api_napi_make_callback
diff --git a/src/env.h b/src/env.h
index d475fc78fb9..d99bb63db6f 100644
--- a/src/env.h
+++ b/src/env.h
@@ -152,6 +152,7 @@ constexpr size_t kFsStatsBufferLength =
V(contextify_context_private_symbol, "node:contextify:context") \
V(contextify_global_private_symbol, "node:contextify:global") \
V(decorated_private_symbol, "node:decorated") \
+ V(napi_type_tag, "node:napi:type_tag") \
V(napi_wrapper, "node:napi:wrapper") \
V(untransferable_object_private_symbol, "node:untransferableObject") \
diff --git a/src/js_native_api.h b/src/js_native_api.h
index 00dedfbfc71..d0d975c2b45 100644
--- a/src/js_native_api.h
+++ b/src/js_native_api.h
@@ -537,6 +537,16 @@ NAPI_EXTERN napi_status napi_detach_arraybuffer(napi_env env,
NAPI_EXTERN napi_status napi_is_detached_arraybuffer(napi_env env,
napi_value value,
bool* result);
+// Type tagging
+NAPI_EXTERN napi_status napi_type_tag_object(napi_env env,
+ napi_value value,
+ const napi_type_tag* type_tag);
+
+NAPI_EXTERN napi_status
+napi_check_object_type_tag(napi_env env,
+ napi_value value,
+ const napi_type_tag* type_tag,
+ bool* result);
#endif // NAPI_EXPERIMENTAL
EXTERN_C_END
diff --git a/src/js_native_api_types.h b/src/js_native_api_types.h
index 7254019dd78..115ccebf261 100644
--- a/src/js_native_api_types.h
+++ b/src/js_native_api_types.h
@@ -140,4 +140,11 @@ typedef enum {
} napi_key_conversion;
#endif // NAPI_VERSION >= 6
+#ifdef NAPI_EXPERIMENTAL
+typedef struct {
+ uint64_t lower;
+ uint64_t upper;
+} napi_type_tag;
+#endif // NAPI_EXPERIMENTAL
+
#endif // SRC_JS_NATIVE_API_TYPES_H_
diff --git a/src/js_native_api_v8.cc b/src/js_native_api_v8.cc
index 37cd1a1ab22..b8455eb3a69 100644
--- a/src/js_native_api_v8.cc
+++ b/src/js_native_api_v8.cc
@@ -10,6 +10,9 @@
#define CHECK_MAYBE_NOTHING(env, maybe, status) \
RETURN_STATUS_IF_FALSE((env), !((maybe).IsNothing()), (status))
+#define CHECK_MAYBE_NOTHING_WITH_PREAMBLE(env, maybe, status) \
+ RETURN_STATUS_IF_FALSE_WITH_PREAMBLE((env), !((maybe).IsNothing()), (status))
+
#define CHECK_TO_NUMBER(env, context, result, src) \
CHECK_TO_TYPE((env), Number, (context), (result), (src), napi_number_expected)
@@ -2356,6 +2359,72 @@ napi_status napi_create_external(napi_env env,
return napi_clear_last_error(env);
}
+NAPI_EXTERN napi_status napi_type_tag_object(napi_env env,
+ napi_value object,
+ const napi_type_tag* type_tag) {
+ NAPI_PREAMBLE(env);
+ v8::Local<v8::Context> context = env->context();
+ v8::Local<v8::Object> obj;
+ CHECK_TO_OBJECT_WITH_PREAMBLE(env, context, obj, object);
+ CHECK_ARG_WITH_PREAMBLE(env, type_tag);
+
+ auto key = NAPI_PRIVATE_KEY(context, type_tag);
+ auto maybe_has = obj->HasPrivate(context, key);
+ CHECK_MAYBE_NOTHING_WITH_PREAMBLE(env, maybe_has, napi_generic_failure);
+ RETURN_STATUS_IF_FALSE_WITH_PREAMBLE(env,
+ !maybe_has.FromJust(),
+ napi_invalid_arg);
+
+ auto tag = v8::BigInt::NewFromWords(context,
+ 0,
+ 2,
+ reinterpret_cast<const uint64_t*>(type_tag));
+ CHECK_MAYBE_EMPTY_WITH_PREAMBLE(env, tag, napi_generic_failure);
+
+ auto maybe_set = obj->SetPrivate(context, key, tag.ToLocalChecked());
+ CHECK_MAYBE_NOTHING_WITH_PREAMBLE(env, maybe_set, napi_generic_failure);
+ RETURN_STATUS_IF_FALSE_WITH_PREAMBLE(env,
+ maybe_set.FromJust(),
+ napi_generic_failure);
+
+ return GET_RETURN_STATUS(env);
+}
+
+NAPI_EXTERN napi_status
+napi_check_object_type_tag(napi_env env,
+ napi_value object,
+ const napi_type_tag* type_tag,
+ bool* result) {
+ NAPI_PREAMBLE(env);
+ v8::Local<v8::Context> context = env->context();
+ v8::Local<v8::Object> obj;
+ CHECK_TO_OBJECT_WITH_PREAMBLE(env, context, obj, object);
+ CHECK_ARG_WITH_PREAMBLE(env, type_tag);
+ CHECK_ARG_WITH_PREAMBLE(env, result);
+
+ auto maybe_value = obj->GetPrivate(context,
+ NAPI_PRIVATE_KEY(context, type_tag));
+ CHECK_MAYBE_EMPTY_WITH_PREAMBLE(env, maybe_value, napi_generic_failure);
+ v8::Local<v8::Value> val = maybe_value.ToLocalChecked();
+
+ // We consider the type check to have failed unless we reach the line below
+ // where we set whether the type check succeeded or not based on the
+ // comparison of the two type tags.
+ *result = false;
+ if (val->IsBigInt()) {
+ int sign;
+ int size = 2;
+ napi_type_tag tag;
+ val.As<v8::BigInt>()->ToWordsArray(&sign,
+ &size,
+ reinterpret_cast<uint64_t*>(&tag));
+ if (size == 2 && sign == 0)
+ *result = (tag.lower == type_tag->lower && tag.upper == type_tag->upper);
+ }
+
+ return GET_RETURN_STATUS(env);
+}
+
napi_status napi_get_value_external(napi_env env,
napi_value value,
void** result) {
diff --git a/src/js_native_api_v8.h b/src/js_native_api_v8.h
index 83e6a0bd02e..06b8049ec46 100644
--- a/src/js_native_api_v8.h
+++ b/src/js_native_api_v8.h
@@ -148,6 +148,14 @@ napi_status napi_set_last_error(napi_env env, napi_status error_code,
} \
} while (0)
+#define RETURN_STATUS_IF_FALSE_WITH_PREAMBLE(env, condition, status) \
+ do { \
+ if (!(condition)) { \
+ return napi_set_last_error( \
+ (env), try_catch.HasCaught() ? napi_pending_exception : (status)); \
+ } \
+ } while (0)
+
#define CHECK_ENV(env) \
do { \
if ((env) == nullptr) { \
@@ -158,9 +166,17 @@ napi_status napi_set_last_error(napi_env env, napi_status error_code,
#define CHECK_ARG(env, arg) \
RETURN_STATUS_IF_FALSE((env), ((arg) != nullptr), napi_invalid_arg)
+#define CHECK_ARG_WITH_PREAMBLE(env, arg) \
+ RETURN_STATUS_IF_FALSE_WITH_PREAMBLE((env), \
+ ((arg) != nullptr), \
+ napi_invalid_arg)
+
#define CHECK_MAYBE_EMPTY(env, maybe, status) \
RETURN_STATUS_IF_FALSE((env), !((maybe).IsEmpty()), (status))
+#define CHECK_MAYBE_EMPTY_WITH_PREAMBLE(env, maybe, status) \
+ RETURN_STATUS_IF_FALSE_WITH_PREAMBLE((env), !((maybe).IsEmpty()), (status))
+
// NAPI_PREAMBLE is not wrapped in do..while: try_catch must have function scope
#define NAPI_PREAMBLE(env) \
CHECK_ENV((env)); \
@@ -178,6 +194,14 @@ napi_status napi_set_last_error(napi_env env, napi_status error_code,
(result) = maybe.ToLocalChecked(); \
} while (0)
+#define CHECK_TO_TYPE_WITH_PREAMBLE(env, type, context, result, src, status) \
+ do { \
+ CHECK_ARG_WITH_PREAMBLE((env), (src)); \
+ auto maybe = v8impl::V8LocalValueFromJsValue((src))->To##type((context)); \
+ CHECK_MAYBE_EMPTY_WITH_PREAMBLE((env), maybe, (status)); \
+ (result) = maybe.ToLocalChecked(); \
+ } while (0)
+
#define CHECK_TO_FUNCTION(env, result, src) \
do { \
CHECK_ARG((env), (src)); \
@@ -189,6 +213,14 @@ napi_status napi_set_last_error(napi_env env, napi_status error_code,
#define CHECK_TO_OBJECT(env, context, result, src) \
CHECK_TO_TYPE((env), Object, (context), (result), (src), napi_object_expected)
+#define CHECK_TO_OBJECT_WITH_PREAMBLE(env, context, result, src) \
+ CHECK_TO_TYPE_WITH_PREAMBLE((env), \
+ Object, \
+ (context), \
+ (result), \
+ (src), \
+ napi_object_expected)
+
#define CHECK_TO_STRING(env, context, result, src) \
CHECK_TO_TYPE((env), String, (context), (result), (src), napi_string_expected)
diff --git a/test/js-native-api/test_object/test.js b/test/js-native-api/test_object/test.js
index 2cd65af6b34..b7866699527 100644
--- a/test/js-native-api/test_object/test.js
+++ b/test/js-native-api/test_object/test.js
@@ -160,6 +160,24 @@ assert.strictEqual(newObject.test_string, 'test string');
}
{
+ // Verify that objects can be type-tagged and type-tag-checked.
+ const obj1 = test_object.TypeTaggedInstance(0);
+ const obj2 = test_object.TypeTaggedInstance(1);
+
+ // Verify that type tags are correctly accepted.
+ assert.strictEqual(test_object.CheckTypeTag(0, obj1), true);
+ assert.strictEqual(test_object.CheckTypeTag(1, obj2), true);
+
+ // Verify that wrongly tagged objects are rejected.
+ assert.strictEqual(test_object.CheckTypeTag(0, obj2), false);
+ assert.strictEqual(test_object.CheckTypeTag(1, obj1), false);
+
+ // Verify that untagged objects are rejected.
+ assert.strictEqual(test_object.CheckTypeTag(0, {}), false);
+ assert.strictEqual(test_object.CheckTypeTag(1, {}), false);
+}
+
+{
// Verify that normal and nonexistent properties can be deleted.
const sym = Symbol();
const obj = { foo: 'bar', [sym]: 'baz' };
diff --git a/test/js-native-api/test_object/test_object.c b/test/js-native-api/test_object/test_object.c
index 08f619bf7ff..f2ea89d6c60 100644
--- a/test/js-native-api/test_object/test_object.c
+++ b/test/js-native-api/test_object/test_object.c
@@ -1,3 +1,4 @@
+#define NAPI_EXPERIMENTAL
#include <js_native_api.h>
#include "../common.h"
#include <string.h>
@@ -471,6 +472,44 @@ static napi_value TestGetProperty(napi_env env,
return object;
}
+// We create two type tags. They are basically 128-bit UUIDs.
+static const napi_type_tag type_tags[2] = {
+ { 0xdaf987b3cc62481a, 0xb745b0497f299531 },
+ { 0xbb7936c374084d9b, 0xa9548d0762eeedb9 }
+};
+
+static napi_value
+TypeTaggedInstance(napi_env env, napi_callback_info info) {
+ size_t argc = 1;
+ uint32_t type_index;
+ napi_value instance, which_type;
+
+ NAPI_CALL(env, napi_get_cb_info(env, info, &argc, &which_type, NULL, NULL));
+ NAPI_CALL(env, napi_get_value_uint32(env, which_type, &type_index));
+ NAPI_CALL(env, napi_create_object(env, &instance));
+ NAPI_CALL(env, napi_type_tag_object(env, instance, &type_tags[type_index]));
+
+ return instance;
+}
+
+static napi_value
+CheckTypeTag(napi_env env, napi_callback_info info) {
+ size_t argc = 2;
+ bool result;
+ napi_value argv[2], js_result;
+ uint32_t type_index;
+
+ NAPI_CALL(env, napi_get_cb_info(env, info, &argc, argv, NULL, NULL));
+ NAPI_CALL(env, napi_get_value_uint32(env, argv[0], &type_index));
+ NAPI_CALL(env, napi_check_object_type_tag(env,
+ argv[1],
+ &type_tags[type_index],
+ &result));
+ NAPI_CALL(env, napi_get_boolean(env, result, &js_result));
+
+ return js_result;
+}
+
EXTERN_C_START
napi_value Init(napi_env env, napi_value exports) {
napi_property_descriptor descriptors[] = {
@@ -490,6 +529,8 @@ napi_value Init(napi_env env, napi_value exports) {
DECLARE_NAPI_PROPERTY("Unwrap", Unwrap),
DECLARE_NAPI_PROPERTY("TestSetProperty", TestSetProperty),
DECLARE_NAPI_PROPERTY("TestHasProperty", TestHasProperty),
+ DECLARE_NAPI_PROPERTY("TypeTaggedInstance", TypeTaggedInstance),
+ DECLARE_NAPI_PROPERTY("CheckTypeTag", CheckTypeTag),
DECLARE_NAPI_PROPERTY("TestGetProperty", TestGetProperty),
};