-
Notifications
You must be signed in to change notification settings - Fork 30.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
n-api: add ability to remove a wrapping #14658
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -674,6 +674,8 @@ v8::Local<v8::Object> CreateAccessorCallbackData(napi_env env, | |
return cbdata; | ||
} | ||
|
||
int kWrapperFields = 3; | ||
|
||
// Pointer used to identify items wrapped by N-API. Used by FindWrapper and | ||
// napi_wrap(). | ||
const char napi_wrap_name[] = "N-API Wrapper"; | ||
|
@@ -682,16 +684,20 @@ const char napi_wrap_name[] = "N-API Wrapper"; | |
// wrapper would be the first in the chain, but it is OK for other objects to | ||
// be inserted in the prototype chain. | ||
bool FindWrapper(v8::Local<v8::Object> obj, | ||
v8::Local<v8::Object>* result = nullptr) { | ||
v8::Local<v8::Object>* result = nullptr, | ||
v8::Local<v8::Object>* parent = nullptr) { | ||
v8::Local<v8::Object> wrapper = obj; | ||
|
||
do { | ||
v8::Local<v8::Value> proto = wrapper->GetPrototype(); | ||
if (proto.IsEmpty() || !proto->IsObject()) { | ||
return false; | ||
} | ||
if (parent != nullptr) { | ||
*parent = wrapper; | ||
} | ||
wrapper = proto.As<v8::Object>(); | ||
if (wrapper->InternalFieldCount() == 2) { | ||
if (wrapper->InternalFieldCount() == kWrapperFields) { | ||
v8::Local<v8::Value> external = wrapper->GetInternalField(1); | ||
if (external->IsExternal() && | ||
external.As<v8::External>()->Value() == v8impl::napi_wrap_name) { | ||
|
@@ -745,6 +751,29 @@ napi_env GetEnv(v8::Local<v8::Context> context) { | |
return result; | ||
} | ||
|
||
napi_status Unwrap(napi_env env, | ||
napi_value js_object, | ||
void** result, | ||
v8::Local<v8::Object>* wrapper, | ||
v8::Local<v8::Object>* parent = nullptr) { | ||
CHECK_ARG(env, js_object); | ||
CHECK_ARG(env, result); | ||
|
||
v8::Local<v8::Value> value = v8impl::V8LocalValueFromJsValue(js_object); | ||
RETURN_STATUS_IF_FALSE(env, value->IsObject(), napi_invalid_arg); | ||
v8::Local<v8::Object> obj = value.As<v8::Object>(); | ||
|
||
RETURN_STATUS_IF_FALSE( | ||
env, v8impl::FindWrapper(obj, wrapper, parent), napi_invalid_arg); | ||
|
||
v8::Local<v8::Value> unwrappedValue = (*wrapper)->GetInternalField(0); | ||
RETURN_STATUS_IF_FALSE(env, unwrappedValue->IsExternal(), napi_invalid_arg); | ||
|
||
*result = unwrappedValue.As<v8::External>()->Value(); | ||
|
||
return napi_ok; | ||
} | ||
|
||
} // end of namespace v8impl | ||
|
||
// Intercepts the Node-V8 module registration callback. Converts parameters | ||
|
@@ -2266,62 +2295,78 @@ napi_status napi_wrap(napi_env env, | |
// Create a wrapper object with an internal field to hold the wrapped pointer | ||
// and a second internal field to identify the owner as N-API. | ||
v8::Local<v8::ObjectTemplate> wrapper_template; | ||
ENV_OBJECT_TEMPLATE(env, wrap, wrapper_template, 2); | ||
ENV_OBJECT_TEMPLATE(env, wrap, wrapper_template, v8impl::kWrapperFields); | ||
|
||
auto maybe_object = wrapper_template->NewInstance(context); | ||
CHECK_MAYBE_EMPTY(env, maybe_object, napi_generic_failure); | ||
|
||
v8::Local<v8::Object> wrapper = maybe_object.ToLocalChecked(); | ||
wrapper->SetInternalField(1, v8::External::New(isolate, | ||
reinterpret_cast<void*>(const_cast<char*>(v8impl::napi_wrap_name)))); | ||
|
||
// Store the pointer as an external in the wrapper. | ||
wrapper->SetInternalField(0, v8::External::New(isolate, native_object)); | ||
wrapper->SetInternalField(1, v8::External::New(isolate, | ||
reinterpret_cast<void*>(const_cast<char*>(v8impl::napi_wrap_name)))); | ||
|
||
// Insert the wrapper into the object's prototype chain. | ||
v8::Local<v8::Value> proto = obj->GetPrototype(); | ||
CHECK(wrapper->SetPrototype(context, proto).FromJust()); | ||
CHECK(obj->SetPrototype(context, wrapper).FromJust()); | ||
|
||
v8impl::Reference* reference = nullptr; | ||
if (result != nullptr) { | ||
// The returned reference should be deleted via napi_delete_reference() | ||
// ONLY in response to the finalize callback invocation. (If it is deleted | ||
// before then, then the finalize callback will never be invoked.) | ||
// Therefore a finalize callback is required when returning a reference. | ||
CHECK_ARG(env, finalize_cb); | ||
v8impl::Reference* reference = v8impl::Reference::New( | ||
reference = v8impl::Reference::New( | ||
env, obj, 0, false, finalize_cb, native_object, finalize_hint); | ||
*result = reinterpret_cast<napi_ref>(reference); | ||
} else if (finalize_cb != nullptr) { | ||
// Create a self-deleting reference just for the finalize callback. | ||
v8impl::Reference::New( | ||
reference = v8impl::Reference::New( | ||
env, obj, 0, true, finalize_cb, native_object, finalize_hint); | ||
} | ||
|
||
if (reference != nullptr) { | ||
wrapper->SetInternalField(2, v8::External::New(isolate, reference)); | ||
} | ||
|
||
return GET_RETURN_STATUS(env); | ||
} | ||
|
||
napi_status napi_unwrap(napi_env env, napi_value js_object, void** result) { | ||
napi_status napi_unwrap(napi_env env, napi_value obj, void** result) { | ||
// Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw | ||
// JS exceptions. | ||
CHECK_ENV(env); | ||
CHECK_ARG(env, js_object); | ||
CHECK_ARG(env, result); | ||
|
||
v8::Local<v8::Value> value = v8impl::V8LocalValueFromJsValue(js_object); | ||
RETURN_STATUS_IF_FALSE(env, value->IsObject(), napi_invalid_arg); | ||
v8::Local<v8::Object> obj = value.As<v8::Object>(); | ||
v8::Local<v8::Object> wrapper; | ||
return napi_set_last_error(env, v8impl::Unwrap(env, obj, result, &wrapper)); | ||
} | ||
|
||
napi_status napi_remove_wrap(napi_env env, napi_value obj, void** result) { | ||
NAPI_PREAMBLE(env); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is a try-catch needed here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I took it as a hint from V8 that an exception may be thrown in In fact, we should re-examine how we deal with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK, then we need to check whether there is such a possibility. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Most of the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @addaleax out of curiosity and AFAUK, does V8 have any APIs which return The reason I ask is that the only ones I've seen are property get/set/has/delete and There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wait, NM. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes. I was also thinking of the number APIs like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @jasongin But that returns a Maybe because it can throw, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh. Good to know. :) |
||
v8::Local<v8::Object> wrapper; | ||
RETURN_STATUS_IF_FALSE( | ||
env, v8impl::FindWrapper(obj, &wrapper), napi_invalid_arg); | ||
v8::Local<v8::Object> parent; | ||
napi_status status = v8impl::Unwrap(env, obj, result, &wrapper, &parent); | ||
if (status != napi_ok) { | ||
return napi_set_last_error(env, status); | ||
} | ||
|
||
v8::Local<v8::Value> unwrappedValue = wrapper->GetInternalField(0); | ||
RETURN_STATUS_IF_FALSE(env, unwrappedValue->IsExternal(), napi_invalid_arg); | ||
v8::Local<v8::Value> external = wrapper->GetInternalField(2); | ||
if (external->IsExternal()) { | ||
v8impl::Reference::Delete( | ||
static_cast<v8impl::Reference*>(external.As<v8::External>()->Value())); | ||
} | ||
|
||
*result = unwrappedValue.As<v8::External>()->Value(); | ||
if (!parent.IsEmpty()) { | ||
v8::Maybe<bool> maybe = parent->SetPrototype( | ||
env->isolate->GetCurrentContext(), wrapper->GetPrototype()); | ||
CHECK_MAYBE_NOTHING(env, maybe, napi_generic_failure); | ||
if (!maybe.FromMaybe(false)) { | ||
return napi_set_last_error(env, napi_generic_failure); | ||
} | ||
} | ||
|
||
return napi_clear_last_error(env); | ||
return GET_RETURN_STATUS(env); | ||
} | ||
|
||
napi_status napi_create_external(napi_env env, | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why also return the pointer here? That is already available via
napi_wrap()
. Or, maybe these two APIs could be combined, with a boolean parameter that indicates whether to remove it at the same time. (I'm not sure I actually prefer that though.)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess it's to avoid having to make two API calls. In nodejs/abi-stable-node#266 @sampsongao is trying to do a
napi_unwrap()
followed unconditionally by anapi_wrap()
. Knowing that an unconditionalnapi_wrap()
will follow would allow him to drop innapi_remove_wrap()
instead ofnapi_unwrap()
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you're concerned about performance, why not make it work in one
napi_wrap()
call? Maybe a boolean to allow overwriting a previous wrap? Or return a previous wrap value?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mhdawson wanted an explicit
napi_remove_wrap()
, but, I guess an explicittrue
/false
parameter is also, well, explicit.If we do this within
napi_unwrap()
, though, we're treading into semver-major territory - which I OK while in experimental, but still, to be discussed, right?