mirror of https://github.com/nodejs/node.git
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: https://github.com/nodejs/node/issues/28164 Co-authored-by: Anna Henningsen <github@addaleax.net> PR-URL: https://github.com/nodejs/node/pull/28237 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Michael Dawson <michael_dawson@ca.ibm.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Signed-off-by: Gabriel Schulhof <gabriel.schulhof@intel.com>
This commit is contained in:
parent
8b3ad75b03
commit
cc7ec889e8
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
'targets': [
|
||||
{
|
||||
'target_name': 'binding',
|
||||
'sources': [ '../type-tag/binding.c' ]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
'targets': [
|
||||
{
|
||||
'target_name': 'binding',
|
||||
'sources': [ 'binding.c' ]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
213
doc/api/n-api.md
213
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
|
||||
|
|
|
@ -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") \
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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_
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -159,6 +159,24 @@ assert.strictEqual(newObject.test_string, 'test string');
|
|||
assert(wrapper.protoB, true);
|
||||
}
|
||||
|
||||
{
|
||||
// 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();
|
||||
|
|
|
@ -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),
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in New Issue