Skip to content
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

Merged
merged 7 commits into from
Jan 19, 2022

Conversation

leobalter
Copy link
Member

@leobalter leobalter commented Nov 2, 2021

Closes #338

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"*).
Copy link
Member

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?

Copy link
Member Author

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.

Copy link
Member Author

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'

Copy link
Member

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.

Copy link
Member

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.

Copy link
Collaborator

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.

Copy link
Member

@mhofman mhofman Nov 3, 2021

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"*).
Copy link
Member

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"*).
Copy link
Member

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
Comment on lines 139 to 146
1. Let _targetLen_ be ? Get(_Target_, *"length"*).
1. If Type(_targetLen_) is Number, then
1. If _targetLen_ is *+&infin;*<sub>𝔽</sub>, set _L_ to +&infin;.
1. Else if _targetLen_ is *-&infin;*<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).
Copy link
Member

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
Comment on lines 136 to 147
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 *+&infin;*<sub>𝔽</sub>, set _L_ to +&infin;.
1. Else if _targetLen_ is *-&infin;*<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_).
Copy link
Member

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?

Copy link
Member Author

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.

Copy link
Member

@ljharb ljharb Nov 2, 2021

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"*).
Copy link
Member

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.

@leobalter leobalter force-pushed the 338-leo/wrapped-name-length branch from 16733e7 to 66470ac Compare November 2, 2021 19:02
spec.html Outdated
Comment on lines 148 to 149
1. Let _targetName_ be ? Get(_Target_, *"name"*).
1. If Type(_targetName_) is not String, set _targetName_ to the empty String.
Copy link
Member Author

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.

@leobalter
Copy link
Member Author

@ljharb PTAL

spec.html Outdated
</emu-clause>

<emu-clause id="sec-copynameandlength" aoid="CopyNameAndLength">
<h1>CopyNameAndLength ( _F_, _Target_, _prefix_ [ , _argCount_ ] )</h1>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<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>

Copy link
Member Author

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?

Copy link
Member

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.

Comment on lines +142 to +143
1. Let _result_ be CopyNameAndLength(_wrapped_, _Target_, *"wrapped"*).
1. If _result_ is an Abrupt Completion, throw a TypeError exception.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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

Copy link
Member Author

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.

Copy link
Member

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?

Copy link
Member Author

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>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<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.

@leobalter
Copy link
Member Author

@ljharb we have the formatted header now. Is there anything else for this PR?

@Jack-Works
Copy link
Member

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

@mhofman
Copy link
Member

mhofman commented Nov 3, 2021

I'd be opposed to host defined behavior. We want more predictable behavior, not less

@jdalton
Copy link
Member

jdalton commented Nov 3, 2021

I like the being similar to bind approach too.

Copy link
Collaborator

@caridy caridy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@leobalter
Copy link
Member Author

This PR should observe any outcome from #338 (comment)

@leobalter
Copy link
Member Author

This achieved consensus during the TC39 plenary in Dec 2021.

@rwaldron rwaldron merged commit b73a1dc into main Jan 19, 2022
@rwaldron rwaldron deleted the 338-leo/wrapped-name-length branch January 19, 2022 17:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Are wrapped functions supposed to have "name" and "length" instance properties?
8 participants