Skip to content

Commit 1286923

Browse files
author
Gabriel Schulhof
committed
n-api: implement wrapping using private properties
PR-URL: #18311 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Michael Dawson <michael_dawson@ca.ibm.com> Fixes: #14367
1 parent f0b2dd3 commit 1286923

File tree

2 files changed

+52
-110
lines changed

2 files changed

+52
-110
lines changed

src/env.h

+2
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,8 @@ class ModuleWrap;
9191
V(decorated_private_symbol, "node:decorated") \
9292
V(npn_buffer_private_symbol, "node:npnBuffer") \
9393
V(selected_npn_buffer_private_symbol, "node:selectedNpnBuffer") \
94+
V(napi_env, "node:napi:env") \
95+
V(napi_wrapper, "node:napi:wrapper") \
9496

9597
// Strings are per-isolate primitives but Environment proxies them
9698
// for the sake of convenience. Strings should be ASCII-only.

src/node_api.cc

+50-110
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include <vector>
1818
#include "node_api.h"
1919
#include "node_internals.h"
20+
#include "env.h"
2021

2122
static
2223
napi_status napi_set_last_error(napi_env env, napi_status error_code,
@@ -46,6 +47,9 @@ struct napi_env__ {
4647
uv_loop_t* loop = nullptr;
4748
};
4849

50+
#define NAPI_PRIVATE_KEY(context, suffix) \
51+
(node::Environment::GetCurrent((context))->napi_ ## suffix())
52+
4953
#define ENV_OBJECT_TEMPLATE(env, prefix, destination, field_count) \
5054
do { \
5155
if ((env)->prefix ## _template.IsEmpty()) { \
@@ -373,6 +377,10 @@ class Reference : private Finalizer {
373377
}
374378

375379
public:
380+
void* Data() {
381+
return _finalize_data;
382+
}
383+
376384
static Reference* New(napi_env env,
377385
v8::Local<v8::Value> value,
378386
uint32_t initial_refcount,
@@ -732,45 +740,6 @@ v8::Local<v8::Object> CreateAccessorCallbackData(napi_env env,
732740
return cbdata;
733741
}
734742

735-
int kWrapperFields = 3;
736-
737-
// Pointer used to identify items wrapped by N-API. Used by FindWrapper and
738-
// napi_wrap().
739-
const char napi_wrap_name[] = "N-API Wrapper";
740-
741-
// Search the object's prototype chain for the wrapper object. Usually the
742-
// wrapper would be the first in the chain, but it is OK for other objects to
743-
// be inserted in the prototype chain.
744-
static
745-
bool FindWrapper(v8::Local<v8::Object> obj,
746-
v8::Local<v8::Object>* result = nullptr,
747-
v8::Local<v8::Object>* parent = nullptr) {
748-
v8::Local<v8::Object> wrapper = obj;
749-
750-
do {
751-
v8::Local<v8::Value> proto = wrapper->GetPrototype();
752-
if (proto.IsEmpty() || !proto->IsObject()) {
753-
return false;
754-
}
755-
if (parent != nullptr) {
756-
*parent = wrapper;
757-
}
758-
wrapper = proto.As<v8::Object>();
759-
if (wrapper->InternalFieldCount() == kWrapperFields) {
760-
v8::Local<v8::Value> external = wrapper->GetInternalField(1);
761-
if (external->IsExternal() &&
762-
external.As<v8::External>()->Value() == v8impl::napi_wrap_name) {
763-
break;
764-
}
765-
}
766-
} while (true);
767-
768-
if (result != nullptr) {
769-
*result = wrapper;
770-
}
771-
return true;
772-
}
773-
774743
static void DeleteEnv(napi_env env, void* data, void* hint) {
775744
delete env;
776745
}
@@ -787,11 +756,8 @@ napi_env GetEnv(v8::Local<v8::Context> context) {
787756
// because we need to stop hard if either of them is empty.
788757
//
789758
// Re https://github.com/nodejs/node/pull/14217#discussion_r128775149
790-
auto key = v8::Private::ForApi(isolate,
791-
v8::String::NewFromOneByte(isolate,
792-
reinterpret_cast<const uint8_t*>("N-API Environment"),
793-
v8::NewStringType::kInternalized).ToLocalChecked());
794-
auto value = global->GetPrivate(context, key).ToLocalChecked();
759+
auto value = global->GetPrivate(context, NAPI_PRIVATE_KEY(context, env))
760+
.ToLocalChecked();
795761

796762
if (value->IsExternal()) {
797763
result = static_cast<napi_env>(value.As<v8::External>()->Value());
@@ -801,7 +767,8 @@ napi_env GetEnv(v8::Local<v8::Context> context) {
801767

802768
// We must also stop hard if the result of assigning the env to the global
803769
// is either nothing or false.
804-
CHECK(global->SetPrivate(context, key, external).FromJust());
770+
CHECK(global->SetPrivate(context, NAPI_PRIVATE_KEY(context, env), external)
771+
.FromJust());
805772

806773
// Create a self-destructing reference to external that will get rid of the
807774
// napi_env when external goes out of scope.
@@ -811,28 +778,46 @@ napi_env GetEnv(v8::Local<v8::Context> context) {
811778
return result;
812779
}
813780

781+
enum UnwrapAction {
782+
KeepWrap,
783+
RemoveWrap
784+
};
785+
814786
static
815787
napi_status Unwrap(napi_env env,
816788
napi_value js_object,
817789
void** result,
818-
v8::Local<v8::Object>* wrapper,
819-
v8::Local<v8::Object>* parent = nullptr) {
790+
UnwrapAction action) {
791+
NAPI_PREAMBLE(env);
820792
CHECK_ARG(env, js_object);
821-
CHECK_ARG(env, result);
793+
if (action == KeepWrap) {
794+
CHECK_ARG(env, result);
795+
}
796+
797+
v8::Isolate* isolate = env->isolate;
798+
v8::Local<v8::Context> context = isolate->GetCurrentContext();
822799

823800
v8::Local<v8::Value> value = v8impl::V8LocalValueFromJsValue(js_object);
824801
RETURN_STATUS_IF_FALSE(env, value->IsObject(), napi_invalid_arg);
825802
v8::Local<v8::Object> obj = value.As<v8::Object>();
826803

827-
RETURN_STATUS_IF_FALSE(
828-
env, v8impl::FindWrapper(obj, wrapper, parent), napi_invalid_arg);
804+
auto val = obj->GetPrivate(context, NAPI_PRIVATE_KEY(context, wrapper))
805+
.ToLocalChecked();
806+
RETURN_STATUS_IF_FALSE(env, val->IsExternal(), napi_invalid_arg);
807+
Reference* reference =
808+
static_cast<v8impl::Reference*>(val.As<v8::External>()->Value());
829809

830-
v8::Local<v8::Value> unwrappedValue = (*wrapper)->GetInternalField(0);
831-
RETURN_STATUS_IF_FALSE(env, unwrappedValue->IsExternal(), napi_invalid_arg);
810+
if (result) {
811+
*result = reference->Data();
812+
}
832813

833-
*result = unwrappedValue.As<v8::External>()->Value();
814+
if (action == RemoveWrap) {
815+
CHECK(obj->DeletePrivate(context, NAPI_PRIVATE_KEY(context, wrapper))
816+
.FromJust());
817+
Reference::Delete(reference);
818+
}
834819

835-
return napi_ok;
820+
return GET_RETURN_STATUS(env);
836821
}
837822

838823
static
@@ -2391,26 +2376,9 @@ napi_status napi_wrap(napi_env env,
23912376
v8::Local<v8::Object> obj = value.As<v8::Object>();
23922377

23932378
// If we've already wrapped this object, we error out.
2394-
RETURN_STATUS_IF_FALSE(env, !v8impl::FindWrapper(obj), napi_invalid_arg);
2395-
2396-
// Create a wrapper object with an internal field to hold the wrapped pointer
2397-
// and a second internal field to identify the owner as N-API.
2398-
v8::Local<v8::ObjectTemplate> wrapper_template;
2399-
ENV_OBJECT_TEMPLATE(env, wrap, wrapper_template, v8impl::kWrapperFields);
2400-
2401-
auto maybe_object = wrapper_template->NewInstance(context);
2402-
CHECK_MAYBE_EMPTY(env, maybe_object, napi_generic_failure);
2403-
v8::Local<v8::Object> wrapper = maybe_object.ToLocalChecked();
2404-
2405-
// Store the pointer as an external in the wrapper.
2406-
wrapper->SetInternalField(0, v8::External::New(isolate, native_object));
2407-
wrapper->SetInternalField(1, v8::External::New(isolate,
2408-
reinterpret_cast<void*>(const_cast<char*>(v8impl::napi_wrap_name))));
2409-
2410-
// Insert the wrapper into the object's prototype chain.
2411-
v8::Local<v8::Value> proto = obj->GetPrototype();
2412-
CHECK(wrapper->SetPrototype(context, proto).FromJust());
2413-
CHECK(obj->SetPrototype(context, wrapper).FromJust());
2379+
RETURN_STATUS_IF_FALSE(env,
2380+
!obj->HasPrivate(context, NAPI_PRIVATE_KEY(context, wrapper)).FromJust(),
2381+
napi_invalid_arg);
24142382

24152383
v8impl::Reference* reference = nullptr;
24162384
if (result != nullptr) {
@@ -2422,52 +2390,24 @@ napi_status napi_wrap(napi_env env,
24222390
reference = v8impl::Reference::New(
24232391
env, obj, 0, false, finalize_cb, native_object, finalize_hint);
24242392
*result = reinterpret_cast<napi_ref>(reference);
2425-
} else if (finalize_cb != nullptr) {
2426-
// Create a self-deleting reference just for the finalize callback.
2427-
reference = v8impl::Reference::New(
2428-
env, obj, 0, true, finalize_cb, native_object, finalize_hint);
2393+
} else {
2394+
// Create a self-deleting reference.
2395+
reference = v8impl::Reference::New(env, obj, 0, true, finalize_cb,
2396+
native_object, finalize_cb == nullptr ? nullptr : finalize_hint);
24292397
}
24302398

2431-
if (reference != nullptr) {
2432-
wrapper->SetInternalField(2, v8::External::New(isolate, reference));
2433-
}
2399+
CHECK(obj->SetPrivate(context, NAPI_PRIVATE_KEY(context, wrapper),
2400+
v8::External::New(isolate, reference)).FromJust());
24342401

24352402
return GET_RETURN_STATUS(env);
24362403
}
24372404

24382405
napi_status napi_unwrap(napi_env env, napi_value obj, void** result) {
2439-
// Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw
2440-
// JS exceptions.
2441-
CHECK_ENV(env);
2442-
v8::Local<v8::Object> wrapper;
2443-
return napi_set_last_error(env, v8impl::Unwrap(env, obj, result, &wrapper));
2406+
return v8impl::Unwrap(env, obj, result, v8impl::KeepWrap);
24442407
}
24452408

24462409
napi_status napi_remove_wrap(napi_env env, napi_value obj, void** result) {
2447-
NAPI_PREAMBLE(env);
2448-
v8::Local<v8::Object> wrapper;
2449-
v8::Local<v8::Object> parent;
2450-
napi_status status = v8impl::Unwrap(env, obj, result, &wrapper, &parent);
2451-
if (status != napi_ok) {
2452-
return napi_set_last_error(env, status);
2453-
}
2454-
2455-
v8::Local<v8::Value> external = wrapper->GetInternalField(2);
2456-
if (external->IsExternal()) {
2457-
v8impl::Reference::Delete(
2458-
static_cast<v8impl::Reference*>(external.As<v8::External>()->Value()));
2459-
}
2460-
2461-
if (!parent.IsEmpty()) {
2462-
v8::Maybe<bool> maybe = parent->SetPrototype(
2463-
env->isolate->GetCurrentContext(), wrapper->GetPrototype());
2464-
CHECK_MAYBE_NOTHING(env, maybe, napi_generic_failure);
2465-
if (!maybe.FromMaybe(false)) {
2466-
return napi_set_last_error(env, napi_generic_failure);
2467-
}
2468-
}
2469-
2470-
return GET_RETURN_STATUS(env);
2410+
return v8impl::Unwrap(env, obj, result, v8impl::RemoveWrap);
24712411
}
24722412

24732413
napi_status napi_create_external(napi_env env,

0 commit comments

Comments
 (0)