-
Notifications
You must be signed in to change notification settings - Fork 24
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
"A promise resolved with" can unexpectedly run JavaScript #56
Comments
This should be fixed here (well, IDL, as I like to keep saying, as this is really not the right place). All of these should be C++^WRust safe. |
This should probably be fixed in Streams. It'd be bad to introduce a new way of resolving promises which is unlike anything else in JS. (I.e., one which skips the .constructor check, so that subclasses get turned into Promise instances.) Alternately it could be fixed in JS, to return a rejected promise instead of rethrowing the getter exception. |
So creating a new promise and then resolving it always requires a round trip through the JavaScript engine? And if so, resolving in general? That seems sad, but maybe it's okayish as it's always the last step in an algorithm anyway. Might still be surprising though. |
I discovered we have the same problem with "Upon fulfillment", "Upon rejection" and "Transforming", which means any "nothrow" operation which performs these can actually throw, transitively breaking large chunks of the streams standard. We really need a way to say "I promise this is a real Promise, please skip the check". |
I mean, promises are JS objects, so creating them always requires a trip through the JS engine anyway.
Grounding these in more correct things would use PerformPromiseThen, which shouldn't have this problem. |
@domenic an implementation could use a similar strategy to platform objects that are allocated in C++^WRust, and whenever JavaScript needs access it gets a wrapper. |
That's theoretically true, but in practice I'm not aware of any JS engines which have the wrapper/impl distinction for their objects. Regardless, it's best to stick to discussing observable consequences, and I maintain it'd be unfortunate to have a way of resolving promises which behave differently on subclasses than Promise.resolve() does. |
The streams standard has a need to resolve promises it created internally, without being exposed to user script. It knows the promises are not subclasses, because it created them itself. Maybe it is unique in that respect, but IIUC the service worker standard has similar concerns. Anyway, as a stopgap I can add operations to the streams standard which avoid touching Promise.prototype.constructor. Maybe the long-term solution is to add an asynchronous algorithm primitive to INFRA to get a clear separation between the things we want to run user script and the things we don't. |
It seems worth sorting this out as apparently there are different expectations at TC39 and we're apparently using hooks engines are not meant to expose to implement this. |
We could resolve this by separating "A promise resolved with" and "A promise resolved with an arbitrary value". The former would have a step 1 that does "Assert: IsPromise(value) is false." Better name suggestions welcome. In particular my names are not great because I think a lot of specs are currently using "a promise resolved with" on arbitrary values already... |
I think we still need the existing semantics in the implementation of PromiseCall(). Other cases from the Streams Standard:
So I find myself leaning toward "the streams standard should be fixed". However, tightening up the definition here also has value in preventing future mistakes of the same kind. |
https://www.w3.org/2001/tag/doc/promises-guide#shorthand-creating says:
If x is a promise this can cause JavaScript to run, due to the definition of PromiseResolve in ECMASCRIPT, step 2a. Specifically, it calls
Get(x, "constructor")
. IfPromise.prototype.constructor
has been set on the global object, it will be consulted. If a getter has been set, it will be executed.This is a problem for the CreateReadableStream() operation in the streams standard. It has a note that "CreateReadableStream throws an exception if and only if the supplied startAlgorithm throws.". This will not be true if startAlgorithm returns a Promise and the global
Promise.prototype.constructor
has been messed with, which is surprising.It is not clear whether this should be fixed here or in the streams standard.
The text was updated successfully, but these errors were encountered: