-
Notifications
You must be signed in to change notification settings - Fork 67
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
Normative: Wrapped function should reuse target name and length #339
Conversation
This makes WrappedFunctionCreate more consistent with Function.prototype.bind
spec.html
Outdated
1. Set _L_ to max(_targetLenAsInt_, 0). | ||
1. Perform ! SetFunctionLength(_wrapped_, _L_). | ||
1. Let _name_ be ? Get(_Target_, *"name"*). | ||
1. Perform ! SetFunctionName(_wrapped_, _name_, *"wrapped"*). |
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 makes the wrapping back and forth between several shadow realms observable from user code, like "wrapped wrapped wrapped function"
. Is this behavior expected?
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 didn't think of that, and I'm not sure what's the best alternative other than just removing the prefix.
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.
It wouldn't be different to Function#bind
function fn() {}
var bound = fn.bind().bind().bind();
bound.name; // 'bound bound bound fn'
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.
Right, it is the same with bind. I'm just confirming that this doesn't break any security assumptions.
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.
Since anyone could at any time overwrite the name to be "wrapped wrapped wrapped anything", anyone with runtime assumptions about what a configurable name implies is already broken.
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.
btw, the spec was written in a way that implementers can optimize the wrapping to wrap the original target rather than the wrapped one to avoid the different jumps, and we should preserve that. And I believe we should be careful to preserve that.
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.
Prefixing should not prevent any call optimization, right? The name is defined at wrapping time, where the target resolution is at call time.
spec.html
Outdated
1. Let _L_ be 0. | ||
1. Let targetHasLength be ? HasOwnProperty(_Target_, *"length"*). | ||
1. If _targetHasLength_ is *true*, then | ||
1. Let _targetLen_ be ? Get(_Target_, *"length"*). |
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: indentation for steps nested with the condition If _targetHasLength_ is *true*, then
?
spec.html
Outdated
1. Set _L_ to max(_targetLenAsInt_, 0). | ||
1. Perform ! SetFunctionLength(_wrapped_, _L_). | ||
1. Let _name_ be ? Get(_Target_, *"name"*). | ||
1. Perform ! SetFunctionName(_wrapped_, _name_, *"wrapped"*). |
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.
Note that name
can be an object. Consider:
function f() {}
Object.defineProperty(f, 'name', { value: f });
f.name === f
spec.html
Outdated
1. Let _targetLen_ be ? Get(_Target_, *"length"*). | ||
1. If Type(_targetLen_) is Number, then | ||
1. If _targetLen_ is *+∞*<sub>𝔽</sub>, set _L_ to +∞. | ||
1. Else if _targetLen_ is *-∞*<sub>𝔽</sub>, set _L_ to 0. | ||
1. Else, | ||
1. Let _targetLenAsInt_ be ! ToIntegerOrInfinity(_targetLen_). | ||
1. Assert: _targetLenAsInt_ is finite. | ||
1. Set _L_ to max(_targetLenAsInt_, 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.
These lines need to be indented.
spec.html
Outdated
1. Let _L_ be 0. | ||
1. Let targetHasLength be ? HasOwnProperty(_Target_, *"length"*). | ||
1. If _targetHasLength_ is *true*, then | ||
1. Let _targetLen_ be ? Get(_Target_, *"length"*). | ||
1. If Type(_targetLen_) is Number, then | ||
1. If _targetLen_ is *+∞*<sub>𝔽</sub>, set _L_ to +∞. | ||
1. Else if _targetLen_ is *-∞*<sub>𝔽</sub>, set _L_ to 0. | ||
1. Else, | ||
1. Let _targetLenAsInt_ be ! ToIntegerOrInfinity(_targetLen_). | ||
1. Assert: _targetLenAsInt_ is finite. | ||
1. Set _L_ to max(_targetLenAsInt_, 0). | ||
1. Perform ! SetFunctionLength(_wrapped_, _L_). |
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.
Since these exact steps are now repeated both here and in function bind, perhaps it should be factored into an AO?
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 fine doing that but I'd like to first define the final steps of this, as it seems like some tostring will be necessary inbetween.
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 don’t think converting to a string would make sense since afaik bind doesn’t do that - i think you’d want to throw if it was a non-passable value.
Makes sense to wait for the refactor until you see what steps are shared.
spec.html
Outdated
1. Set _L_ to max(_targetLenAsInt_, 0). | ||
1. Perform ! SetFunctionLength(_wrapped_, _L_). | ||
1. Let _name_ be ? Get(_Target_, *"name"*). | ||
1. Perform ! SetFunctionName(_wrapped_, _name_, *"wrapped"*). |
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.
Since anyone could at any time overwrite the name to be "wrapped wrapped wrapped anything", anyone with runtime assumptions about what a configurable name implies is already broken.
16733e7
to
66470ac
Compare
spec.html
Outdated
1. Let _targetName_ be ? Get(_Target_, *"name"*). | ||
1. If Type(_targetName_) is not String, set _targetName_ to the empty String. |
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.
@ljharb this is interesting! Function#bind
normalizes to only copy string names, so I also applied this change here as you mentioned in the comments above.
There is a catch where we need to avoid bringing error objects from the target function and that can happen on the HasOwnProperty and Get calls.
I believe that doing the common abstraction - as you suggested - would make it easier to handle this. I'll follow up with a new commit soon.
@ljharb PTAL |
spec.html
Outdated
</emu-clause> | ||
|
||
<emu-clause id="sec-copynameandlength" aoid="CopyNameAndLength"> | ||
<h1>CopyNameAndLength ( _F_, _Target_, _prefix_ [ , _argCount_ ] )</h1> |
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.
<h1>CopyNameAndLength ( _F_, _Target_, _prefix_ [ , _argCount_ ] )</h1> | |
<h1> | |
CopyNameAndLength ( | |
_F_: a function object, | |
_Target_: a function object, | |
_prefix_: a String, | |
optional _argCount_: a Number, | |
) | |
</h1> |
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 this a new notation?
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.
yes, the entire spec uses it for AOs since tc39/ecma262#545 was merged in July.
1. Let _result_ be CopyNameAndLength(_wrapped_, _Target_, *"wrapped"*). | ||
1. If _result_ is an Abrupt Completion, throw a TypeError exception. |
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.
1. Let _result_ be CopyNameAndLength(_wrapped_, _Target_, *"wrapped"*). | |
1. If _result_ is an Abrupt Completion, throw a TypeError exception. | |
1. Perform ? CopyNameAndLength(_wrapped_, _Target_, *"wrapped"*). |
currently this is catching the almost-certainly-TypeError and throwing a TypeError, which would be masking the actual cause
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.
the issue here is not with TypeError, is not leaking cross realms error objects.
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.
ah, i see. shouldn't the message, or the cause, or the type (if it's a builtin) be preserved if possible?
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.
there is no definition for the message, same as in PerformShadowRealmEval
.
There is no logic yet for the cause, and I'd prefer to do that in a separate PR and tackle all these points throwing TypeError.
spec.html
Outdated
</emu-clause> | ||
|
||
<emu-clause id="sec-shadowrealm-objects"> | ||
<h1>ShadowRealm Objects</h1> | ||
<emu-clause id="sec-shadowrealm-abstracts"> | ||
<h1>ShadowRealm Abstract Operations</h1> | ||
|
||
<emu-clause id="sec-wrappedfunctioncreate" aoid="WrappedFunctionCreate"> | ||
<h1>WrappedFunctionCreate ( _callerRealm_, _Target_ )</h1> |
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.
<h1>WrappedFunctionCreate ( _callerRealm_, _Target_ )</h1> | |
<h1> | |
WrappedFunctionCreate ( | |
_callerRealm_: a Realm Record, | |
_Target_: an ECMAScript language value, | |
) | |
</h1> |
and then the assertion on line 134 can be removed.
@ljharb we have the formatted header now. Is there anything else for this PR? |
What's the prior art of this change? If it's performance consideration, I think it should be host defined, host either copy the name and length or do nothings |
I'd be opposed to host defined behavior. We want more predictable behavior, not less |
I like the being similar to |
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
This PR should observe any outcome from #338 (comment) |
This achieved consensus during the TC39 plenary in Dec 2021. |
Closes #338