-
Notifications
You must be signed in to change notification settings - Fork 29.6k
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
Object.getOwnPropertyDescriptor throws TypeError on process _handle #17636
Comments
You are seeing an interplay of a lot of different edge cases, in the ECMAScript spec, in the APIs V8 provides, and in how we use V8's APIs. In the end, I don't believe this is a "bug" per se, but there might be some ways we can improve this (admittedly surprising for ordinary users) behavior. I will first explain why this behavior is allowed by the ECMAScript spec, and then demonstrate two ways how we can fix this. Primer on the ECMAScript specificationIn ECMAScript, every object has a certain set of "internal methods" that the rest of the specification call on to do certain tasks. For example, you can see the definition of
which means that the [[GetOwnProperty]] internal method of the object obj is called with the parameter key. (The "?" is significant; I will get to it later.) The ECMAScript spec divides all objects into two camps: "ordinary objects" and "exotic objects." (Further reading: 6.1.7 The Object Type.) Most of the objects you encounter are ordinary objects, which means that their internal methods are the default ones. However, ECMAScript spec also defines a few kinds of "exotic objects," which may override the default implementations of those internal methods. There are certain minimal constraints put on what exotic objects are allowed to do, but in general the overriden internal methods can do a lot of things without going against the spec. Array objects are one of them, because of the special semantics involved in the property The spec also allows implementations (like V8) to define their own exotic objects, and also users to do the same through Now, about that ?. Per 5.2 Algorithm Conventions, ? is really a shorthand for "if doing this results in an 'abrupt completion' (like a thrown exception), then stop what we are doing and allow that abrupt completion to propagate; otherwise carry on". That is why this interpretation is false:
Even though Why getting
|
t->PrototypeTemplate()->SetAccessor(env->bytes_read_string(), | |
GetBytesRead<Base>, | |
nullptr, | |
env->as_external(), | |
v8::DEFAULT, | |
attributes, | |
signature); |
The resulting effect of such an operation is somewhere in between [].length
and Map.prototype.size
: bytesRead
is specified on the prototype so Object.getOwnPropertyDescriptor(process.stdin._handle, 'bytesRead')
won't return anything, yet it is a magical parameter (rather than JavaScript getter/setter pair) on process.stdin._handle.__proto__
so that any attempt to get information about the property will go through the V8 mechanism (i.e., the resulting process.stdin._handle.__proto__
is made an exotic object). And because we specified the signature
parameter when adding the accessor property, V8 will check whether the validity of the this
before calling the C++ function.
How do we fix this
I hope I have sufficiently demonstrated why the current behavior is not a "bug". The way SetAccessor()
works is well-defined, and allowed by the ECMAScript spec. But I admit this is surprising for users.
Without trying either of them out, I think there are two ways to improve this situation: making the property more like Map.prototype.size
, and making the property more like [].length
. Some benchmark may be needed on deciding which one is more memory- and computing-efficient though. In either method, the prototype object will become an ordinary object.
-
Making the property more like
Map.prototype.size
: create getter/setter pair on prototype object. V8 offers av8::Template::SetAccessorProperty()
that creates a JavaScript-observable getter/setter pair from C++ getter/setter callbacks. In contrast tov8::ObjectTemplate::SetAccessor()
,SetAccessorProperty()
does not make the resulting object an exotic object; and the created getter/setter pair will be visible fromObject.getOwnPropertyDescriptor(process.stdin._handle.__proto__, 'bytesRead')
. The instance object would still be an ordinary object.On the other hand, this new function requires a different C++ callback signature from
SetAccessor()
. Changing the signature is totally doable however. -
Making the property more like
[].length
: move the accessor property from the prototype object to the actual instance. Rather than callingSetAccessor()
on thet->PrototypeTemplate()
, we could call it ont->InstanceTemplate()
. This way,bytesRead
will be observed as an own property ofprocess.stdin._handle
by virtue ofprocess.stdin._handle
becoming an exotic object, and there would be no chance for the current error to be thrown.
This makes sense to me and thank you for the thoughtful reply and ECMAScript primer as well! Certainly the change was surprising - seeing My naive assumption was that I'll go ahead and handle this error properly, and we can close this issue if you'd like. Thanks again! |
I deliberately moved them in the reverse direction recently, see #16482. |
The solution proposed in number 2 is indeed the easiest way out of this (and would likely be accepted widely without fuss, given that it was the previous behaviour that no one complained about), and is indeed what @bnoordhuis changed in #16482 (which caused the failed assert and thus fixing it with the throw in #16860). I agree that accessing a property and having that throw is unexpected and the pre 8.9 behaviour of returning
The pre 8.9 behaviour is that the above works perfectly. However, the 8.9.0 behaviour is that node crashes, and 8.9.2 behaviour is that it throws, both of which are failure modes for the above configuration. It happens because node-config accesses properties in the proto (https://github.com/lorenwest/node-config/blob/master/lib/config.js#L1216 #16949 (comment)) to detect cyclical structures (also catching the thrown error here causes a loop, because now child and parent can never match). I've tried getting the same pre 8.9 behaviour with A summary of the above would be, can we get |
Would making |
That doesn’t sound like it removes the throw on when accessing the property? I don’t think that’s a solution then, Fwiw, I’d prefer 1., but would be fine with either of the options listed by @TimothyGu |
For some reason I thought SetAccessorProperty() didn't exist in v4.x but it's there. That's a good solution then. That said, |
This is exactly the kind of potential problems I tried to point out in
Agreed, but the elephant in the room is compatibility. I'm fine with solution 1. |
I gave solution 1 a shot, and as discussed above, using |
@bnoordhuis Any tips on where to look for making |
@jure That said, it may not be worth it since the issue has been fixed and |
PR-URL: nodejs/node#17665 Fixes: nodejs/node#17636 Refs: nodejs/node#16482 Refs: nodejs/node#16860 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Timothy Gu <timothygu99@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
PR-URL: nodejs#17665 Fixes: nodejs#17636 Refs: nodejs#16482 Refs: nodejs#16860 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Timothy Gu <timothygu99@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
PR-URL: nodejs/node#17665 Fixes: nodejs/node#17636 Refs: nodejs/node#16482 Refs: nodejs/node#16860 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Timothy Gu <timothygu99@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
* src: replace SetAccessor w/ SetAccessorProperty PR-URL: nodejs/node#17665 Fixes: nodejs/node#17636 Refs: nodejs/node#16482 Refs: nodejs/node#16860 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Timothy Gu <timothygu99@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl> * test: make test-tls-external-accessor agnostic Remove reliance on V8-specific error messages in test/parallel/test-tls-external-accessor.js. Check that the error is a `TypeError`. The test should now be successful without modification using ChakraCore. Backport-PR-URL: nodejs/node#20456 PR-URL: nodejs/node#16272 Reviewed-By: Michaël Zasso <targos@protonmail.com> Reviewed-By: Refael Ackermann <refack@gmail.com> Reviewed-By: Yuta Hiroto <hello@about-hiroppy.com> Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com> Reviewed-By: Luigi Pinca <luigipinca@gmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Tobias Nießen <tniessen@tnie.de>
>=8.9.2
)Hello!
Related to #16949 and #16860 - it appears that since Node 8 there have been some issues with
Object.getOwnPropertyDescriptor
.The simplest test case is
Expected output:
undefined
(pre 8.9 behavior)Pre 8.9.2, this crashes node with a Segmentation Fault (see linked issues)
On and after 8.9.2, this returns TypeError:
This means that calling
Object.getOwnPropertyDescripton
on a process handle fails.At least according to https://tc39.github.io/ecma262/#sec-object.getownpropertydescriptor - I don't see any specification for throwing a TypeError here (particularly since the Prototype is of course an Object, it even seems to violate the ES5 spec)
Can we return to returning undefined here?
After thinking about it, I believe returning a proper descriptor with a value of
0
would actually be preferable toundefined
, sincebytesRead
is a getter which returns a Number. This is the case with, for examplelength
:Thank you for your time!
The text was updated successfully, but these errors were encountered: