Inconsistent gating of `Set(_, "lastIndex", _, true)` in Regular Expressions breaks frozen instances.
Thanks for the bug report! The fix for this is on github (tc39/ecma262#627) and on the agenda for this week’s meeting. I hope this will be fixed by next week (though there are some web compat concerns).
Thanks, Brian
From: es-discuss [mailto:es-discuss-bounces at mozilla.org] On Behalf Of Duane Leslie
Sent: Sunday, February 28, 2016 4:16 PM
To: es-discuss at mozilla.org
Subject: Inconsistent gating of Set(_, "lastIndex", _, true) in Regular Expressions breaks frozen instances.
In most cases [[Set]] operations on the lastIndex property of regular expressions is gated by the 'global' and/or 'sticky' flags*. This should mean that a non-global/non-stick regular expression can be safely frozen. Unfortunately two cases have not been gated, and in both of these cases the operation is a no-op except that it results in a thrown TypeError.
Specifically, '21.2.5.2.2 Runtime Semantics: RegExpBuiltinExec', step 12.a.i sets the lastIndex to 0 if the match fails to occur regardless of the flags or the original state of lastIndex. If the regular expression is non-global/non-sticky the lastIndex property can never be non-zero so this is a no-op that throws.
Additionally, in '21.2.5.9 RegExp.prototype [ @@search ]' steps 5 & 7 blindly reset and restore the lastIndex value without checking the global or sticky flags. Assuming the RegExpBuiltinExec is used then Step 5 is a no-op that throws since the lastIndex cannot be non-zero, and step 7 is a no-op that throws since the lastIndex can only be set to 0 (or not modified) by RegExpBuiltinExec. The attached NOTE states "The lastIndex property is left unchanged" which is not entirely true.
The problem with the throw in RegExpBuiltinExec that particularly troubles me is that it only occurs if the match fails. So long as the match succeeds a frozen non-global/non-sticky regular expression will work correctly through this method. If however the match fails instead of returning null a TypeError will be thrown.
For completeness, the only other non-gate [[Set]] of lastIndex is in '21.2.3.2.2 Runtime Semantics: RegExpInitialize' step 12 which is only called with an existing object through 'B.2.5.1 RegExp.prototype.compile'. I have no position either way on whether this instance should be gated somehow. All of the other initialized values are internal properties and so unaffected by being frozen, so maybe it is best that the lastIndex always throws so that a frozen RegExp cannot be changed by a call to .compile().
[*] Gated [[Set]]:
21.2.5.2.2 step 12.c.i.1 (sticky)
21.2.5.2.2 step 15 (global || sticky)
21.2.5.6 step 6.b (global)
21.2.5.6 step 6.e.iii.4.c (global)
21.2.5.8 step 8.b (global)
21.2.5.8 step 11.c.iii.2.c (global)
21.2.5.11 step 19.a (sticky, supplied rx is copied first)
,
Duane.
In most cases [[Set]] operations on the
lastIndexproperty of regular expressions is gated by the 'global' and/or 'sticky' flags*. This should mean that a non-global/non-stick regular expression can be safely frozen. Unfortunately two cases have not been gated, and in both of these cases the operation is a no-op except that it results in a thrown TypeError.Specifically, '21.2.5.2.2 Runtime Semantics: RegExpBuiltinExec', step 12.a.i sets the
lastIndexto 0 if the match fails to occur regardless of the flags or the original state oflastIndex. If the regular expression is non-global/non-sticky thelastIndexproperty can never be non-zero so this is a no-op that throws.Additionally, in '21.2.5.9 RegExp.prototype [ @@search ]' steps 5 & 7 blindly reset and restore the
lastIndexvalue without checking the global or sticky flags. Assuming theRegExpBuiltinExecis used then Step 5 is a no-op that throws since thelastIndexcannot be non-zero, and step 7 is a no-op that throws since thelastIndexcan only be set to 0 (or not modified) byRegExpBuiltinExec. The attached NOTE states "ThelastIndexproperty is left unchanged" which is not entirely true.The problem with the throw in
RegExpBuiltinExecthat particularly troubles me is that it only occurs if the match fails. So long as the match succeeds a frozen non-global/non-sticky regular expression will work correctly through this method. If however the match fails instead of returningnulla TypeError will be thrown.For completeness, the only other non-gate [[Set]] of
lastIndexis in '21.2.3.2.2 Runtime Semantics: RegExpInitialize' step 12 which is only called with an existing object through 'B.2.5.1 RegExp.prototype.compile'. I have no position either way on whether this instance should be gated somehow. All of the other initialized values are internal properties and so unaffected by being frozen, so maybe it is best that thelastIndexalways throws so that a frozenRegExpcannot be changed by a call to.compile().[*] Gated [[Set]]: 21.2.5.2.2 step 12.c.i.1 (sticky) 21.2.5.2.2 step 15 (global || sticky) 21.2.5.6 step 6.b (global) 21.2.5.6 step 6.e.iii.4.c (global) 21.2.5.8 step 8.b (global) 21.2.5.8 step 11.c.iii.2.c (global) 21.2.5.11 step 19.a (sticky, supplied
rxis copied first),
Duane.
In most cases [[Set]] operations on the `lastIndex` property of regular expressions is gated by the 'global' and/or 'sticky' flags*. This should mean that a non-global/non-stick regular expression can be safely frozen. Unfortunately two cases have not been gated, and in both of these cases the operation is a no-op except that it results in a thrown TypeError. Specifically, '21.2.5.2.2 Runtime Semantics: RegExpBuiltinExec', step 12.a.i sets the `lastIndex` to 0 if the match fails to occur regardless of the flags or the original state of `lastIndex`. If the regular expression is non-global/non-sticky the `lastIndex` property can never be non-zero so this is a no-op that throws. Additionally, in '21.2.5.9 RegExp.prototype [ @@search ]' steps 5 & 7 blindly reset and restore the `lastIndex` value without checking the global or sticky flags. Assuming the `RegExpBuiltinExec` is used then Step 5 is a no-op that throws since the `lastIndex` cannot be non-zero, and step 7 is a no-op that throws since the `lastIndex` can only be set to 0 (or not modified) by `RegExpBuiltinExec`. The attached NOTE states "The `lastIndex` property is left unchanged" which is not entirely true. The problem with the throw in `RegExpBuiltinExec` that particularly troubles me is that it only occurs if the match fails. So long as the match succeeds a frozen non-global/non-sticky regular expression will work correctly through this method. If however the match fails instead of returning `null` a TypeError will be thrown. For completeness, the only other non-gate [[Set]] of `lastIndex` is in '21.2.3.2.2 Runtime Semantics: RegExpInitialize' step 12 which is only called with an existing object through 'B.2.5.1 RegExp.prototype.compile'. I have no position either way on whether this instance should be gated somehow. All of the other initialized values are internal properties and so unaffected by being frozen, so maybe it is best that the `lastIndex` always throws so that a frozen `RegExp` cannot be changed by a call to `.compile()`. [*] Gated [[Set]]: 21.2.5.2.2 step 12.c.i.1 (sticky) 21.2.5.2.2 step 15 (global || sticky) 21.2.5.6 step 6.b (global) 21.2.5.6 step 6.e.iii.4.c (global) 21.2.5.8 step 8.b (global) 21.2.5.8 step 11.c.iii.2.c (global) 21.2.5.11 step 19.a (sticky, supplied `rx` is copied first) Regards, Duane. -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20160229/5d0e0941/attachment.html>