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:
authorGabriel Schulhof <gabriel.schulhof@intel.com>2019-06-15 02:44:18 +0300
committerGabriel Schulhof <gabriel.schulhof@intel.com>2020-08-01 04:30:30 +0300
commitcc7ec889e863433c248bc4b5c8e33f61ccc40f29 (patch)
tree308069adb8a8948f965504ada6835a5d30c6d10e /doc/api/n-api.md
parent8b3ad75b03ee49688ac0e9e4aa2481231fd4ff96 (diff)
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>
Diffstat (limited to 'doc/api/n-api.md')
-rw-r--r--doc/api/n-api.md213
1 files changed, 213 insertions, 0 deletions
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