Skip to content

Commit

Permalink
n-api: support type-tagging objects
Browse files Browse the repository at this point in the history
`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: nodejs#28164
Co-authored-by: Anna Henningsen <github@addaleax.net>
PR-URL: nodejs#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>
  • Loading branch information
Gabriel Schulhof and addaleax committed Aug 1, 2020
1 parent 8b3ad75 commit cc7ec88
Show file tree
Hide file tree
Showing 14 changed files with 545 additions and 0 deletions.
8 changes: 8 additions & 0 deletions benchmark/napi/type-tag-check/binding.gyp
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
'targets': [
{
'target_name': 'binding',
'sources': [ '../type-tag/binding.c' ]
}
]
}
18 changes: 18 additions & 0 deletions benchmark/napi/type-tag-check/index.js
Original file line number Diff line number Diff line change
@@ -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);
}
84 changes: 84 additions & 0 deletions benchmark/napi/type-tag/binding.c
Original file line number Diff line number Diff line change
@@ -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;
}
8 changes: 8 additions & 0 deletions benchmark/napi/type-tag/binding.gyp
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
'targets': [
{
'target_name': 'binding',
'sources': [ 'binding.c' ]
}
]
}
18 changes: 18 additions & 0 deletions benchmark/napi/type-tag/check-object-tag.js
Original file line number Diff line number Diff line change
@@ -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);
}
18 changes: 18 additions & 0 deletions benchmark/napi/type-tag/index.js
Original file line number Diff line number Diff line change
@@ -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);
}
Loading

0 comments on commit cc7ec88

Please sign in to comment.