Skip to content

Commit 5c183d7

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

File tree

3 files changed

+52
-111
lines changed

3 files changed

+52
-111
lines changed

src/env.h

+2
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ namespace node {
6262
V(npn_buffer_private_symbol, "node:npnBuffer") \
6363
V(processed_private_symbol, "node:processed") \
6464
V(selected_npn_buffer_private_symbol, "node:selectedNpnBuffer") \
65+
V(napi_env, "node:napi:env") \
66+
V(napi_wrapper, "node:napi:wrapper") \
6567

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

src/node_api.cc

+49-111
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
#include "node_internals.h"
2121
#include "env-inl.h"
2222
#include "node_api_backport.h"
23-
#include "util.h"
2423

2524
static
2625
napi_status napi_set_last_error(napi_env env, napi_status error_code,
@@ -53,6 +52,9 @@ struct napi_env__ {
5352
uv_loop_t* loop = nullptr;
5453
};
5554

55+
#define NAPI_PRIVATE_KEY(context, suffix) \
56+
(node::Environment::GetCurrent((context))->napi_ ## suffix())
57+
5658
#define ENV_OBJECT_TEMPLATE(env, prefix, destination, field_count) \
5759
do { \
5860
if ((env)->prefix ## _template.IsEmpty()) { \
@@ -383,6 +385,10 @@ class Reference : private Finalizer {
383385
}
384386

385387
public:
388+
void* Data() {
389+
return _finalize_data;
390+
}
391+
386392
static Reference* New(napi_env env,
387393
v8::Local<v8::Value> value,
388394
uint32_t initial_refcount,
@@ -742,45 +748,6 @@ v8::Local<v8::Object> CreateAccessorCallbackData(napi_env env,
742748
return cbdata;
743749
}
744750

745-
int kWrapperFields = 3;
746-
747-
// Pointer used to identify items wrapped by N-API. Used by FindWrapper and
748-
// napi_wrap().
749-
const char napi_wrap_name[] = "N-API Wrapper";
750-
751-
// Search the object's prototype chain for the wrapper object. Usually the
752-
// wrapper would be the first in the chain, but it is OK for other objects to
753-
// be inserted in the prototype chain.
754-
static
755-
bool FindWrapper(v8::Local<v8::Object> obj,
756-
v8::Local<v8::Object>* result = nullptr,
757-
v8::Local<v8::Object>* parent = nullptr) {
758-
v8::Local<v8::Object> wrapper = obj;
759-
760-
do {
761-
v8::Local<v8::Value> proto = wrapper->GetPrototype();
762-
if (proto.IsEmpty() || !proto->IsObject()) {
763-
return false;
764-
}
765-
if (parent != nullptr) {
766-
*parent = wrapper;
767-
}
768-
wrapper = proto.As<v8::Object>();
769-
if (wrapper->InternalFieldCount() == kWrapperFields) {
770-
v8::Local<v8::Value> external = wrapper->GetInternalField(1);
771-
if (external->IsExternal() &&
772-
external.As<v8::External>()->Value() == v8impl::napi_wrap_name) {
773-
break;
774-
}
775-
}
776-
} while (true);
777-
778-
if (result != nullptr) {
779-
*result = wrapper;
780-
}
781-
return true;
782-
}
783-
784751
static void DeleteEnv(napi_env env, void* data, void* hint) {
785752
delete env;
786753
}
@@ -797,11 +764,8 @@ napi_env GetEnv(v8::Local<v8::Context> context) {
797764
// because we need to stop hard if either of them is empty.
798765
//
799766
// Re https://github.com/nodejs/node/pull/14217#discussion_r128775149
800-
auto key = v8::Private::ForApi(isolate,
801-
v8::String::NewFromOneByte(isolate,
802-
reinterpret_cast<const uint8_t*>("N-API Environment"),
803-
v8::NewStringType::kInternalized).ToLocalChecked());
804-
auto value = global->GetPrivate(context, key).ToLocalChecked();
767+
auto value = global->GetPrivate(context, NAPI_PRIVATE_KEY(context, env))
768+
.ToLocalChecked();
805769

806770
if (value->IsExternal()) {
807771
result = static_cast<napi_env>(value.As<v8::External>()->Value());
@@ -811,7 +775,8 @@ napi_env GetEnv(v8::Local<v8::Context> context) {
811775

812776
// We must also stop hard if the result of assigning the env to the global
813777
// is either nothing or false.
814-
CHECK(global->SetPrivate(context, key, external).FromJust());
778+
CHECK(global->SetPrivate(context, NAPI_PRIVATE_KEY(context, env), external)
779+
.FromJust());
815780

816781
// Create a self-destructing reference to external that will get rid of the
817782
// napi_env when external goes out of scope.
@@ -821,28 +786,46 @@ napi_env GetEnv(v8::Local<v8::Context> context) {
821786
return result;
822787
}
823788

789+
enum UnwrapAction {
790+
KeepWrap,
791+
RemoveWrap
792+
};
793+
824794
static
825795
napi_status Unwrap(napi_env env,
826796
napi_value js_object,
827797
void** result,
828-
v8::Local<v8::Object>* wrapper,
829-
v8::Local<v8::Object>* parent = nullptr) {
798+
UnwrapAction action) {
799+
NAPI_PREAMBLE(env);
830800
CHECK_ARG(env, js_object);
831-
CHECK_ARG(env, result);
801+
if (action == KeepWrap) {
802+
CHECK_ARG(env, result);
803+
}
804+
805+
v8::Isolate* isolate = env->isolate;
806+
v8::Local<v8::Context> context = isolate->GetCurrentContext();
832807

833808
v8::Local<v8::Value> value = v8impl::V8LocalValueFromJsValue(js_object);
834809
RETURN_STATUS_IF_FALSE(env, value->IsObject(), napi_invalid_arg);
835810
v8::Local<v8::Object> obj = value.As<v8::Object>();
836811

837-
RETURN_STATUS_IF_FALSE(
838-
env, v8impl::FindWrapper(obj, wrapper, parent), napi_invalid_arg);
812+
auto val = obj->GetPrivate(context, NAPI_PRIVATE_KEY(context, wrapper))
813+
.ToLocalChecked();
814+
RETURN_STATUS_IF_FALSE(env, val->IsExternal(), napi_invalid_arg);
815+
Reference* reference =
816+
static_cast<v8impl::Reference*>(val.As<v8::External>()->Value());
839817

840-
v8::Local<v8::Value> unwrappedValue = (*wrapper)->GetInternalField(0);
841-
RETURN_STATUS_IF_FALSE(env, unwrappedValue->IsExternal(), napi_invalid_arg);
818+
if (result) {
819+
*result = reference->Data();
820+
}
842821

843-
*result = unwrappedValue.As<v8::External>()->Value();
822+
if (action == RemoveWrap) {
823+
CHECK(obj->DeletePrivate(context, NAPI_PRIVATE_KEY(context, wrapper))
824+
.FromJust());
825+
Reference::Delete(reference);
826+
}
844827

845-
return napi_ok;
828+
return GET_RETURN_STATUS(env);
846829
}
847830

848831
static
@@ -2399,26 +2382,9 @@ napi_status napi_wrap(napi_env env,
23992382
v8::Local<v8::Object> obj = value.As<v8::Object>();
24002383

24012384
// If we've already wrapped this object, we error out.
2402-
RETURN_STATUS_IF_FALSE(env, !v8impl::FindWrapper(obj), napi_invalid_arg);
2403-
2404-
// Create a wrapper object with an internal field to hold the wrapped pointer
2405-
// and a second internal field to identify the owner as N-API.
2406-
v8::Local<v8::ObjectTemplate> wrapper_template;
2407-
ENV_OBJECT_TEMPLATE(env, wrap, wrapper_template, v8impl::kWrapperFields);
2408-
2409-
auto maybe_object = wrapper_template->NewInstance(context);
2410-
CHECK_MAYBE_EMPTY(env, maybe_object, napi_generic_failure);
2411-
v8::Local<v8::Object> wrapper = maybe_object.ToLocalChecked();
2412-
2413-
// Store the pointer as an external in the wrapper.
2414-
wrapper->SetInternalField(0, v8::External::New(isolate, native_object));
2415-
wrapper->SetInternalField(1, v8::External::New(isolate,
2416-
reinterpret_cast<void*>(const_cast<char*>(v8impl::napi_wrap_name))));
2417-
2418-
// Insert the wrapper into the object's prototype chain.
2419-
v8::Local<v8::Value> proto = obj->GetPrototype();
2420-
CHECK(wrapper->SetPrototype(context, proto).FromJust());
2421-
CHECK(obj->SetPrototype(context, wrapper).FromJust());
2385+
RETURN_STATUS_IF_FALSE(env,
2386+
!obj->HasPrivate(context, NAPI_PRIVATE_KEY(context, wrapper)).FromJust(),
2387+
napi_invalid_arg);
24222388

24232389
v8impl::Reference* reference = nullptr;
24242390
if (result != nullptr) {
@@ -2430,52 +2396,24 @@ napi_status napi_wrap(napi_env env,
24302396
reference = v8impl::Reference::New(
24312397
env, obj, 0, false, finalize_cb, native_object, finalize_hint);
24322398
*result = reinterpret_cast<napi_ref>(reference);
2433-
} else if (finalize_cb != nullptr) {
2434-
// Create a self-deleting reference just for the finalize callback.
2435-
reference = v8impl::Reference::New(
2436-
env, obj, 0, true, finalize_cb, native_object, finalize_hint);
2399+
} else {
2400+
// Create a self-deleting reference.
2401+
reference = v8impl::Reference::New(env, obj, 0, true, finalize_cb,
2402+
native_object, finalize_cb == nullptr ? nullptr : finalize_hint);
24372403
}
24382404

2439-
if (reference != nullptr) {
2440-
wrapper->SetInternalField(2, v8::External::New(isolate, reference));
2441-
}
2405+
CHECK(obj->SetPrivate(context, NAPI_PRIVATE_KEY(context, wrapper),
2406+
v8::External::New(isolate, reference)).FromJust());
24422407

24432408
return GET_RETURN_STATUS(env);
24442409
}
24452410

24462411
napi_status napi_unwrap(napi_env env, napi_value obj, void** result) {
2447-
// Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw
2448-
// JS exceptions.
2449-
CHECK_ENV(env);
2450-
v8::Local<v8::Object> wrapper;
2451-
return napi_set_last_error(env, v8impl::Unwrap(env, obj, result, &wrapper));
2412+
return v8impl::Unwrap(env, obj, result, v8impl::KeepWrap);
24522413
}
24532414

24542415
napi_status napi_remove_wrap(napi_env env, napi_value obj, void** result) {
2455-
NAPI_PREAMBLE(env);
2456-
v8::Local<v8::Object> wrapper;
2457-
v8::Local<v8::Object> parent;
2458-
napi_status status = v8impl::Unwrap(env, obj, result, &wrapper, &parent);
2459-
if (status != napi_ok) {
2460-
return napi_set_last_error(env, status);
2461-
}
2462-
2463-
v8::Local<v8::Value> external = wrapper->GetInternalField(2);
2464-
if (external->IsExternal()) {
2465-
v8impl::Reference::Delete(
2466-
static_cast<v8impl::Reference*>(external.As<v8::External>()->Value()));
2467-
}
2468-
2469-
if (!parent.IsEmpty()) {
2470-
v8::Maybe<bool> maybe = parent->SetPrototype(
2471-
env->isolate->GetCurrentContext(), wrapper->GetPrototype());
2472-
CHECK_MAYBE_NOTHING(env, maybe, napi_generic_failure);
2473-
if (!maybe.FromMaybe(false)) {
2474-
return napi_set_last_error(env, napi_generic_failure);
2475-
}
2476-
}
2477-
2478-
return GET_RETURN_STATUS(env);
2416+
return v8impl::Unwrap(env, obj, result, v8impl::RemoveWrap);
24792417
}
24802418

24812419
napi_status napi_create_external(napi_env env,

src/node_api_backport.h

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
// N-API.
77

88
#include "node_internals.h"
9+
#include "env-inl.h"
910

1011
namespace node {
1112

0 commit comments

Comments
 (0)