-
Notifications
You must be signed in to change notification settings - Fork 30.1k
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: Implement stricter wrapping #13872
Conversation
// Search the object's prototype chain for the wrapper object. Usually the | ||
// 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, |
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 we used a v8::FunctionTemplate
instead of a simple string we could take advantage of v8::Object::FindInstanceInPrototypeChain(Local< FunctionTemplate> tmpl)
instead of having to perform the descent ourselves.
The problem with that is that the FunctionTemplate
it requires needs to be stored somewhere. Now, it can be stored on the napi_env
but the problem with that is that the env is per-module. So, if one module does napi_wrap()
, then the other module will not recognize that this has happened, because it will have its own copy of a FunctionTemplate
serving the same purpose. Is this desirable or is this to be avoided?
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.
@nodejs/addon-api
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'd say it we are better off depending on something that prevents things from working across modules.
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 meant to say not depending on something that prevents things from working across modules. I think that's why you avoided the function template in the PR so all should be good.
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.
So, if one module does napi_wrap(), then the other module will not recognize that this has happened, because it will have its own copy of a FunctionTemplate serving the same purpose.
It doesn't have to be. FunctionTemplates can be per-isolate (don't need to be per-context) so if you manage to associate some state with the isolate, you should be good.
As to how to do that: you could use v8::Isolate::SetData()
but that's a scarce resource (only 4 slots and one is already taken.)
Perhaps a static std::map<v8::Isolate*, State*>
guarded by a mutex? You only need to look it up once per napi_env
because you can cache it inside the env. Lock contention shouldn't be an issue.
9d6d6f1
to
5fd44ce
Compare
will wait to do complete review until updated to return an error on second wrap as discussed in the N-API meeting today. |
5fd44ce
to
ad01722
Compare
@mhdawson done now. |
doc/api/n-api.md
Outdated
*Note*: Calling `napi_wrap()` a second time on an object that already has a | ||
native instance associated with it by virtue of a previous call to | ||
`napi_wrap()` will have the effect that the new instance will replace the old | ||
instance, and the old instance will eventually be freed by the JavaScript |
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 think this needs to be updated to reflect an error will be thrown.
src/node_api.cc
Outdated
// Store the pointer as an external in the wrapper. If an external is already | ||
// there, this will replace it and the old external will eventually be gc-ed. | ||
// At that point the associated pointer will be finalized if a finalize_cb was | ||
// specified at the time of wrapping. |
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.
This should be updated as well. Since we should error out above.
@@ -0,0 +1,6 @@ | |||
{ |
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 think we should add this test to test_general instead of using a new addon. Each addon makes the compile just a little bit longer.
ad01722
to
19fb553
Compare
@mhdawson I have addressed your comments. |
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.
LGTM
// Search the object's prototype chain for the wrapper object. Usually the | ||
// 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, |
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 meant to say not depending on something that prevents things from working across modules. I think that's why you avoided the function template in the PR so all should be good.
@gabrielschulhof sorry this dropped off my radar, needs a rebase and a new CI run. |
19fb553
to
023c1bf
Compare
I had an unrelated test failure when I ran
|
@mhdawson I've rebased, but I suspect a test run now will likely run into this unrelated error. |
Needs one more rebase since I landed the other PR we talked about first. |
023c1bf
to
ee07177
Compare
@mhdawson It's rebased now, and when I ran |
src/node_api.cc
Outdated
@@ -673,6 +673,38 @@ v8::Local<v8::Object> CreateAccessorCallbackData(napi_env env, | |||
return cbdata; | |||
} | |||
|
|||
// Pointer used to identify items wrapped by N-API. Used by FindWrapper and | |||
// napi_wrap(). | |||
char napi_wrap_name[] = "N-API Wrapper"; |
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.
Nit: const char*
?
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 left out the const
because converting from const char*
to void*
requires a pile of casting:
reinterpret_cast<void*>(const_cast<char*>(v8impl::napi_wrap_name))
But anyway, I'll add it.
napi_value wrap(napi_env env, napi_callback_info info) { | ||
size_t argc = 2; | ||
napi_value argv[2]; | ||
napi_ref payload, callback; |
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.
Is callback
used anywhere?
ee07177
to
c8f4446
Compare
Looks like @gabrielschulhof pushed a commit to address @TimothyGu's comments. CI run - https://ci.nodejs.org/job/node-test-pull-request/9030/ |
Is this OK to land? |
doc/api/n-api.md
Outdated
@@ -2905,6 +2905,10 @@ required in order to enable correct proper of the reference. | |||
Afterward, additional manipulation of the wrapper's prototype chain may cause | |||
`napi_unwrap()` to fail. | |||
|
|||
*Note*: Calling `napi_wrap()` a second time on an object that already has a | |||
native instance associated with it by virtue of a previous call to | |||
`napi_wrap()` will cause an error to be thrown. |
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.
error to be returned?
src/node_api.cc
Outdated
|
||
// Store the pointer as an external in the wrapper. | ||
v8::Local<v8::Value> external = v8::External::New(isolate, native_object); | ||
wrapper->SetInternalField(0, external); |
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 are you overwriting the External created and stored just a few lines up?
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'm not. A few lines up I'm setting the other index (1) whereas here I'm setting (0).
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.
Oh, dang! Merge error. You're right!
Use a stronger criterion to identify objects in the prototype chain that store pointers to native data that were added by previous calls to `napi_wrap()`. Whereas the old criterion for identifying `napi_wrap()`-injected prototype chain objects was to consider an object with an internal field count of 1 to be such an object, the new criterion is to consider an object with an internal field count of 2 such that the second field holds a `v8::External` which itself contains a pointer to a global static string unique to N-API to be a `napi_wrap()`-injected prototype chain object. This greatly reduces the possibility of returning a pointer that was not previously added with `napi_wrap()`, and it allows us to recognize that an object has already undergone `napi_wrap()` and we can thus prevent a chain of wrappers only the first of which is accessible from appearing in the prototype chain, as would be the result of multiple calls to `napi_wrap()` using the same object.
c8f4446
to
10e30e5
Compare
@TimothyGu should be good now, but it needs another CI run. |
Running CI at https://ci.nodejs.org/job/node-test-pull-request/9078/. Might take a while though because of the backlog from the CI lockdown. |
Can you lowercase the subsystem in the commit? |
@evanlucas will do |
Landed as d5b397c |
Use a stronger criterion to identify objects in the prototype chain that store pointers to native data that were added by previous calls to `napi_wrap()`. Whereas the old criterion for identifying `napi_wrap()`-injected prototype chain objects was to consider an object with an internal field count of 1 to be such an object, the new criterion is to consider an object with an internal field count of 2 such that the second field holds a `v8::External` which itself contains a pointer to a global static string unique to N-API to be a `napi_wrap()`-injected prototype chain object. This greatly reduces the possibility of returning a pointer that was not previously added with `napi_wrap()`, and it allows us to recognize that an object has already undergone `napi_wrap()` and we can thus prevent a chain of wrappers only the first of which is accessible from appearing in the prototype chain, as would be the result of multiple calls to `napi_wrap()` using the same object. PR-URL: #13872 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Michael Dawson <[email protected]> Reviewed-By: Timothy Gu <[email protected]>
@bnoordhuis I just noticed your comment about not recognizing the wrap between 2 modules after landing. I had discussed this with Gabriel and the way the check works that is not a problem since we don't check against the FunctionTemplate itself. Having said that @gabrielschulhof does make sense for us to look at the suggestion to see if that makes thing better. |
Use a stronger criterion to identify objects in the prototype chain that store pointers to native data that were added by previous calls to `napi_wrap()`. Whereas the old criterion for identifying `napi_wrap()`-injected prototype chain objects was to consider an object with an internal field count of 1 to be such an object, the new criterion is to consider an object with an internal field count of 2 such that the second field holds a `v8::External` which itself contains a pointer to a global static string unique to N-API to be a `napi_wrap()`-injected prototype chain object. This greatly reduces the possibility of returning a pointer that was not previously added with `napi_wrap()`, and it allows us to recognize that an object has already undergone `napi_wrap()` and we can thus prevent a chain of wrappers only the first of which is accessible from appearing in the prototype chain, as would be the result of multiple calls to `napi_wrap()` using the same object. PR-URL: #13872 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Michael Dawson <[email protected]> Reviewed-By: Timothy Gu <[email protected]>
Use a stronger criterion to identify objects in the prototype chain that store pointers to native data that were added by previous calls to `napi_wrap()`. Whereas the old criterion for identifying `napi_wrap()`-injected prototype chain objects was to consider an object with an internal field count of 1 to be such an object, the new criterion is to consider an object with an internal field count of 2 such that the second field holds a `v8::External` which itself contains a pointer to a global static string unique to N-API to be a `napi_wrap()`-injected prototype chain object. This greatly reduces the possibility of returning a pointer that was not previously added with `napi_wrap()`, and it allows us to recognize that an object has already undergone `napi_wrap()` and we can thus prevent a chain of wrappers only the first of which is accessible from appearing in the prototype chain, as would be the result of multiple calls to `napi_wrap()` using the same object. PR-URL: #13872 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Michael Dawson <[email protected]> Reviewed-By: Timothy Gu <[email protected]>
Store the `napi_env` on the global object at a private key. This gives us one `napi_env` per context. Re: nodejs#13872 (comment)
Use a stronger criterion to identify objects in the prototype chain that store pointers to native data that were added by previous calls to `napi_wrap()`. Whereas the old criterion for identifying `napi_wrap()`-injected prototype chain objects was to consider an object with an internal field count of 1 to be such an object, the new criterion is to consider an object with an internal field count of 2 such that the second field holds a `v8::External` which itself contains a pointer to a global static string unique to N-API to be a `napi_wrap()`-injected prototype chain object. This greatly reduces the possibility of returning a pointer that was not previously added with `napi_wrap()`, and it allows us to recognize that an object has already undergone `napi_wrap()` and we can thus prevent a chain of wrappers only the first of which is accessible from appearing in the prototype chain, as would be the result of multiple calls to `napi_wrap()` using the same object. PR-URL: nodejs#13872 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Michael Dawson <[email protected]> Reviewed-By: Timothy Gu <[email protected]>
Use a stronger criterion to identify objects in the prototype chain that store pointers to native data that were added by previous calls to `napi_wrap()`. Whereas the old criterion for identifying `napi_wrap()`-injected prototype chain objects was to consider an object with an internal field count of 1 to be such an object, the new criterion is to consider an object with an internal field count of 2 such that the second field holds a `v8::External` which itself contains a pointer to a global static string unique to N-API to be a `napi_wrap()`-injected prototype chain object. This greatly reduces the possibility of returning a pointer that was not previously added with `napi_wrap()`, and it allows us to recognize that an object has already undergone `napi_wrap()` and we can thus prevent a chain of wrappers only the first of which is accessible from appearing in the prototype chain, as would be the result of multiple calls to `napi_wrap()` using the same object. Backport-PR-URL: #19447 PR-URL: #13872 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Michael Dawson <[email protected]> Reviewed-By: Timothy Gu <[email protected]>
Use a stronger criterion to identify objects in the prototype chain that store
pointers to native data that were added by previous calls to
napi_wrap()
.Whereas the old criterion for identifying
napi_wrap()
-injected prototypechain objects was to consider an object with an internal field
count of 1 to be such an object, the new criterion is to consider an object
with an internal field count of 2 such that the second field holds a
v8::External
which itself contains a pointer to a global static string uniqueto N-API to be a
napi_wrap()
-injected prototype chain object.This greatly reduces the possibility of returning a pointer that was not
previously added with
napi_wrap()
, and it allows us to recognize that anobject has already undergone
napi_wrap()
and we can thus replace the oldv8::External
with the new one such that, when the old one will be garbage-collected, any finalize callback for its native pointer will also be called.
Checklist
make -j4 test
(UNIX), orvcbuild test
(Windows) passesAffected core subsystem(s)