Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion Core/Node-API/Include/Shared/napi/js_native_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -613,8 +613,9 @@ NAPI_EXTERN napi_status NAPI_CDECL
napi_is_detached_arraybuffer(napi_env env, napi_value value, bool* result);
#endif // NAPI_VERSION >= 7

#if NAPI_VERSION >= 8
// Type tagging
// [BABYLON-NATIVE-ADDITION]: exposed unconditionally, see napi_type_tag in
// js_native_api_types.h.
NAPI_EXTERN napi_status NAPI_CDECL napi_type_tag_object(
napi_env env, napi_value value, const napi_type_tag* type_tag);

Expand All @@ -623,6 +624,8 @@ napi_check_object_type_tag(napi_env env,
napi_value value,
const napi_type_tag* type_tag,
bool* result);

#if NAPI_VERSION >= 8
NAPI_EXTERN napi_status NAPI_CDECL napi_object_freeze(napi_env env,
napi_value object);
NAPI_EXTERN napi_status NAPI_CDECL napi_object_seal(napi_env env,
Expand Down
6 changes: 4 additions & 2 deletions Core/Node-API/Include/Shared/napi/js_native_api_types.h
Original file line number Diff line number Diff line change
Expand Up @@ -163,11 +163,13 @@ typedef enum {
} napi_key_conversion;
#endif // NAPI_VERSION >= 6

#if NAPI_VERSION >= 8
// [BABYLON-NATIVE-ADDITION]: type tags are implemented by every engine here, so
// they are exposed unconditionally rather than at NAPI_VERSION >= 8. The tree
// stays at NAPI_VERSION 5 because napi_get_version reports that macro and the
// rest of v6/v7/v8 is not implemented on all engines.
typedef struct {
uint64_t lower;
uint64_t upper;
} napi_type_tag;
#endif // NAPI_VERSION >= 8

#endif // SRC_JS_NATIVE_API_TYPES_H_
6 changes: 2 additions & 4 deletions Core/Node-API/Include/Shared/napi/napi-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -1343,8 +1343,8 @@ inline TypeTaggable::TypeTaggable() : Value() {}
inline TypeTaggable::TypeTaggable(napi_env _env, napi_value _value)
: Value(_env, _value) {}

#if NAPI_VERSION >= 8

// [BABYLON-NATIVE-ADDITION]: exposed unconditionally, see napi_type_tag in
// js_native_api_types.h.
inline void TypeTaggable::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);
Expand All @@ -1358,8 +1358,6 @@ inline bool TypeTaggable::CheckTypeTag(const napi_type_tag* type_tag) const {
return result;
}

#endif // NAPI_VERSION >= 8

////////////////////////////////////////////////////////////////////////////////
// Object class
////////////////////////////////////////////////////////////////////////////////
Expand Down
5 changes: 3 additions & 2 deletions Core/Node-API/Include/Shared/napi/napi.h
Original file line number Diff line number Diff line change
Expand Up @@ -758,10 +758,11 @@ class Symbol : public Name {

class TypeTaggable : public Value {
public:
#if NAPI_VERSION >= 8
// [BABYLON-NATIVE-ADDITION]: exposed unconditionally, see napi_type_tag in
// js_native_api_types.h.
void TypeTag(const napi_type_tag* type_tag) const;
bool CheckTypeTag(const napi_type_tag* type_tag) const;
#endif // NAPI_VERSION >= 8

protected:
TypeTaggable();
TypeTaggable(napi_env env, napi_value value);
Expand Down
12 changes: 12 additions & 0 deletions Core/Node-API/Source/env_quickjs.cc
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,18 @@ namespace Napi
env_ptr->has_own_property_function = JS_UNDEFINED;
}

if (!JS_IsUndefined(env_ptr->type_tag_map))
{
JS_FreeValue(env_ptr->context, env_ptr->type_tag_get);
JS_FreeValue(env_ptr->context, env_ptr->type_tag_set);
JS_FreeValue(env_ptr->context, env_ptr->type_tag_has);
JS_FreeValue(env_ptr->context, env_ptr->type_tag_map);
env_ptr->type_tag_get = JS_UNDEFINED;
env_ptr->type_tag_set = JS_UNDEFINED;
env_ptr->type_tag_has = JS_UNDEFINED;
env_ptr->type_tag_map = JS_UNDEFINED;
}

// Free all remaining JSValues in the handle scope stack
for (auto& ptr : env_ptr->handle_scope_stack)
{
Expand Down
136 changes: 136 additions & 0 deletions Core/Node-API/Source/js_native_api_chakra.cc
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
#include "js_native_api_chakra.h"
#include "js_native_api_type_tag.h"
#include <napi/js_native_api.h>
#include <array>
#include <cassert>
#include <cmath>
#include <cstring>
#include <optional>
#include <vector>
#include <string>
Expand Down Expand Up @@ -1780,6 +1782,140 @@ napi_status napi_remove_wrap(napi_env env, napi_value js_object, void** result)
return napi_ok;
}

// Type tags
//
// The tag lives in a WeakMap reachable only from napi_env__ (see
// js_native_api_type_tag.h). Non-object inputs are coerced exactly as the V8
// port's CHECK_TO_OBJECT does, so all engines agree on those edge cases.
static napi_status EnsureTypeTagMap(napi_env env) {
if (env->type_tag_map != JS_INVALID_REFERENCE) {
return napi_ok;
}

JsValueRef global = JS_INVALID_REFERENCE;
CHECK_JSRT(env, JsGetGlobalObject(&global));

JsPropertyIdRef weakMapId = JS_INVALID_REFERENCE;
CHECK_JSRT(env, JsCreatePropertyId(STR_AND_LENGTH("WeakMap"), &weakMapId));

JsValueRef weakMapCtor = JS_INVALID_REFERENCE;
CHECK_JSRT(env, JsGetProperty(global, weakMapId, &weakMapCtor));

JsValueType ctorType;
CHECK_JSRT(env, JsGetValueType(weakMapCtor, &ctorType));
RETURN_STATUS_IF_FALSE(env, ctorType == JsFunction, napi_generic_failure);

JsValueRef undefinedValue = JS_INVALID_REFERENCE;
CHECK_JSRT(env, JsGetUndefinedValue(&undefinedValue));

JsValueRef map = JS_INVALID_REFERENCE;
CHECK_JSRT(env, JsConstructObject(weakMapCtor, &undefinedValue, 1, &map));

JsPropertyIdRef getId = JS_INVALID_REFERENCE;
JsPropertyIdRef setId = JS_INVALID_REFERENCE;
JsPropertyIdRef hasId = JS_INVALID_REFERENCE;
CHECK_JSRT(env, JsCreatePropertyId(STR_AND_LENGTH("get"), &getId));
CHECK_JSRT(env, JsCreatePropertyId(STR_AND_LENGTH("set"), &setId));
CHECK_JSRT(env, JsCreatePropertyId(STR_AND_LENGTH("has"), &hasId));

JsValueRef get = JS_INVALID_REFERENCE;
JsValueRef set = JS_INVALID_REFERENCE;
JsValueRef has = JS_INVALID_REFERENCE;
CHECK_JSRT(env, JsGetProperty(map, getId, &get));
CHECK_JSRT(env, JsGetProperty(map, setId, &set));
CHECK_JSRT(env, JsGetProperty(map, hasId, &has));

// The map is unreachable from the JS heap, so it needs an explicit root. There
// is no matching JsRelease: Napi::Detach runs after JsDisposeRuntime (see
// AppRuntime_Chakra.cpp), so by then the runtime and every object in it are
// already gone and releasing would fault. Same reason the wrap symbol taken in
// Napi::Attach is never released.
CHECK_JSRT(env, JsAddRef(map, nullptr));
CHECK_JSRT(env, JsAddRef(get, nullptr));
CHECK_JSRT(env, JsAddRef(set, nullptr));
CHECK_JSRT(env, JsAddRef(has, nullptr));

env->type_tag_map = map;
env->type_tag_get = get;
env->type_tag_set = set;
env->type_tag_has = has;
return napi_ok;
}

napi_status napi_type_tag_object(napi_env env,
napi_value object,
const napi_type_tag* type_tag) {
CHECK_ENV(env);
CHECK_ARG(env, object);
CHECK_ARG(env, type_tag);

CHECK_NAPI(EnsureTypeTagMap(env));

JsValueRef target = JS_INVALID_REFERENCE;
CHECK_JSRT(env, JsConvertValueToObject(reinterpret_cast<JsValueRef>(object), &target));

JsValueRef args[3] = {env->type_tag_map, target, JS_INVALID_REFERENCE};

JsValueRef tagged = JS_INVALID_REFERENCE;
CHECK_JSRT(env, JsCallFunction(env->type_tag_has, args, 2, &tagged));
bool alreadyTagged = false;
CHECK_JSRT(env, JsBooleanToBool(tagged, &alreadyTagged));
RETURN_STATUS_IF_FALSE(env, !alreadyTagged, napi_invalid_arg);

char hex[napi_type_tag_util::kHexLength + 1];
napi_type_tag_util::ToHex(type_tag, hex);

JsValueRef tagValue = JS_INVALID_REFERENCE;
CHECK_JSRT(env, JsCreateString(hex, napi_type_tag_util::kHexLength, &tagValue));

args[2] = tagValue;
JsValueRef stored = JS_INVALID_REFERENCE;
CHECK_JSRT(env, JsCallFunction(env->type_tag_set, args, 3, &stored));

return napi_ok;
}

napi_status napi_check_object_type_tag(napi_env env,
napi_value object,
const napi_type_tag* type_tag,
bool* result) {
CHECK_ENV(env);
CHECK_ARG(env, object);
CHECK_ARG(env, type_tag);
CHECK_ARG(env, result);

*result = false;

JsValueRef target = JS_INVALID_REFERENCE;
CHECK_JSRT(env, JsConvertValueToObject(reinterpret_cast<JsValueRef>(object), &target));

// Nothing has been tagged yet, so nothing can match.
if (env->type_tag_map == JS_INVALID_REFERENCE) {
return napi_ok;
}

JsValueRef args[2] = {env->type_tag_map, target};
JsValueRef stored = JS_INVALID_REFERENCE;
CHECK_JSRT(env, JsCallFunction(env->type_tag_get, args, 2, &stored));

JsValueType storedType;
CHECK_JSRT(env, JsGetValueType(stored, &storedType));
if (storedType != JsString) {
return napi_ok;
}

char actual[napi_type_tag_util::kHexLength + 1] = {};
size_t written = 0;
CHECK_JSRT(env, JsCopyString(stored, actual, sizeof(actual), &written));

char expected[napi_type_tag_util::kHexLength + 1];
napi_type_tag_util::ToHex(type_tag, expected);

*result = written == napi_type_tag_util::kHexLength &&
memcmp(actual, expected, napi_type_tag_util::kHexLength) == 0;
return napi_ok;
}

napi_status napi_create_external(napi_env env,
void* data,
napi_finalize finalize_cb,
Expand Down
7 changes: 7 additions & 0 deletions Core/Node-API/Source/js_native_api_chakra.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ struct napi_env__ {

JsPropertyIdRef wrap_property_id = JS_INVALID_REFERENCE;

// Type-tag store, created on first use. See js_native_api_type_tag.h for why
// this is a WeakMap held here rather than a property on the tagged object.
JsValueRef type_tag_map = JS_INVALID_REFERENCE;
JsValueRef type_tag_get = JS_INVALID_REFERENCE;
JsValueRef type_tag_set = JS_INVALID_REFERENCE;
JsValueRef type_tag_has = JS_INVALID_REFERENCE;

// Escapable scope bookkeeping: token -> whether that scope has escaped. Values
// are rooted by the engine rather than by a scope here, so this exists only to
// honour the one-escape-per-scope rule and to reject tokens that are not open.
Expand Down
Loading
Loading