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

Annex E incompatibilities should include "early Reference Error" #257

Open
saurik opened this issue Dec 16, 2015 · 13 comments
Open

Annex E incompatibilities should include "early Reference Error" #257

saurik opened this issue Dec 16, 2015 · 13 comments

Comments

@saurik
Copy link

saurik commented Dec 16, 2015

In earlier ECMAScript versions, the syntax ++++a would lead to a ReferenceError when executed, but was considered valid code: if it existed in some large script and was never used, it would not lead to a failure. In ECMAScript 2015, this behavior has been changed to require an "early Reference Error" per 12.4.1, 12.5.1, and 12.14.1. As far as I can tell, this has not been marked as being an incompatibility.

Worse, while we would pray that people were not relying on this strange syntax, Chrome has judged that in at least one case this behavior is not compatible with the legacy web :/. In researching why V8 was not throwing errors in some circumstances (such as how 12.3.1.5 requires that function calls return false for IsValidSimpleAssignmentTarget), I determined that Chrome backed out this change a while back.

https://codereview.chromium.org/217823003

Make invalid LHSs that are calls late errors
Necessary for web legacy compatibility.

V8 version 4.9.0 (candidate) [sample shell]
> if (false) a() = 0; true
true

Mozilla actually has a similar compatibility case in SpiderMonkey, though I can't find any discussion to find out exactly why. (They modify IsValidSimpleAssignmentTarget to gate on a feature flag they call "PermitAssignmentToFunctionCalls", which is in turn controlled by whether a "keyed destructuring" is in place, though I haven't yet taken the time to figure out what is going on there... but the syntax works ;P.)

https://dxr.mozilla.org/mozilla-central/rev/97e537f85183ef31481602ab9e5587a6e7d16b4d/js/src/frontend/Parser.cpp#7272

js> if (false) a() = 0; true
true

There is a mention in Annex E that "6.2.3: In ECMAScript 2015, Function calls are not allowed to return a Reference value.", but I do not believe that is a sufficient notice of the incompatibility (and would bet it is not intended to address this case, but I could be wrong?). I also did not see anything in Annex B that "for web compatibility reasons some of these early errors are relaxed", but maybe I'm just being dense.

(The versions of JavaScriptCore I have easy access to currently, which may or may not be terribly up-to-date, simply do not implement any of these early error semantics, so they would not have run into any compatibility concerns that I can try to dig up and report.)

@saurik saurik changed the title Annex D incompatibilities should include "early Reference Error" Annex E incompatibilities should include "early Reference Error" Dec 16, 2015
@anba
Copy link
Contributor

anba commented Dec 16, 2015

In earlier ECMAScript versions, the syntax ++++a would lead to a ReferenceError when executed, but was considered valid code: if it existed in some large script and was never used, it would not lead to a failure. In ECMAScript 2015,

This is specified as an early error since ECMAScript 5 (ES5, ch. 16). ECMAScript 3 made it an implementation dependent choice whether or not to report early errors for invalid assignments. Specifically this sentence from ES3:

An implementation may treat any instance of the following kinds of runtime errors as a syntax error and therefore report it early:

was changed in ES5 to:

An implementation must treat any instance of the following kinds of errors as an early error:

The assignment to a function call is a special case:
ES5 allowed function calls to return a Reference value (ES5, ch. 8.6.2, Table 9), this feature was removed in ES2015 as documented in Annex E.

@littledan
Copy link
Member

It's still a dilemma in Chrome whether to prohibit these sorts of things. Does anyone have data about whether they are used on the web?

@allenwb
Copy link
Member

allenwb commented Dec 16, 2015

So this has all been extensively discussed in TC39 when these changes were introduced.

When the change from optional early error to mandatory early error was made in ES5, I believe the state of the web was that some of the "top 5" browsers (that was the criteria we used back then) reported early errors and others did not. That meant that code that that depended upon late error treatment was not interoperable across browsers on the web. In other words, it was a non-breaking change.

Reference returning host functions (and the syntax that allowed their invocation) was in the spec. to support VBScript compatibility in early versions of IE. I'm pretty sure you can find meeting notes that document this decision.

@littledan
Copy link
Member

Thanks for the references, Allen.

Unfortunately, not all browsers can ship something just because some other browser in the past has shipped it. In Chrome, we have to worry about compatibility with subsets of the web which currently work on Chrome, such as websites which are written for the 'mobile web', which in practice means WebKit-based browsers. In other parts of the web platform, WebKit prefixes are unfortunately being standardized for similar reasons. "Websites which work on non-Presto browsers" would probably be another similar subset (not sure how much that affected ES2015 decisions). Sometimes these subsets of web usage have things in common with each other which certain browsers don't follow. We would really like to both adhere to standards and not break the web.

Is there a strong reason why this has to be an early error? What sort of bad consequences would come if we made these runtime errors in the spec?

@ajklein
Copy link
Contributor

ajklein commented Mar 10, 2016

I just tested current SpiderMonkey, JSC, V8, and Chakra, and all fail to throw an error for the following code:

function f() {
  g() = 5;
}

They all of course throw an error if f is called.

Given the above, it seems like the most reasonable thing would be to specify this behavior. If it must be in an Annex, so be it, but I don't think we have any evidence that early errors in such cases are web-compatible. Until someone presents such evidence, it seems safest to specify the current browser behavior.

@ajklein
Copy link
Contributor

ajklein commented Dec 21, 2016

@bterlson any thoughts here? Would you merge a PR that made this change, or should I put this on the agenda for a future meeting?

@evilpie
Copy link
Contributor

evilpie commented Dec 22, 2016

I just wanted to note that SpiderMonkey doesn't parse this syntax in strict mode.

@bterlson
Copy link
Member

@ajklein We should make a proposal PR. There will be debate about where to put it though (annex B vs. main spec) so it's needs-consensus for sure. Sure would be good to get data about how common this is.  We were able to make some changes like this in the past - eg. SM/JSC was able to fix the re-evaluation of LHS references issue.

@littledan
Copy link
Member

@bakkot implemented data collection in V8 for the f() = x case, separating out strict and sloppy mode. If all goes well, we should have some good data in four months or so. Does anyone have more information about the ++++x case?

@bakkot
Copy link
Contributor

bakkot commented Dec 27, 2016

@littledan, in ES3 the grammar included UnaryExpression : ++ UnaryExpression, and no Early Errors or anything of the kind to restrict it syntactically. But as far as I'm aware there was no environment in which this was ever anything other than an error at runtime (unlike f() = 0), so browsers either never supported it or stopped supporting it sometime long past. That one, and related things like ++~x etc., are almost certainly safe to leave as-is.

Edit: although as this issue was originally about, those cases should be marked in Annex E.

kisg pushed a commit to paul99/v8mips that referenced this issue Dec 27, 2016
This syntax was formerly legal per ECMAScript, but has been a
SyntaxError for some time now. V8 deviates from spec in that it
is instead a runtime error; we'd like to know if we can get
away with removing it (at least in sloppy mode) or if the spec
should be changed.

c.f. tc39/ecma262#257 (comment)

Also add self to authors file

BUG=v8:4480

Review-Url: https://codereview.chromium.org/2599253002
Cr-Commit-Position: refs/heads/master@{#41960}
zdobersek pushed a commit to Igalia/webkit that referenced this issue May 17, 2019
https://bugs.webkit.org/show_bug.cgi?id=197603

Reviewed by Keith Miller.

JSTests:

* test262/expectations.yaml:
Update expectations to reflect new SyntaxErrors.
(Ideally, these should all be viewed as passing in the near future.)

* stress/async-await-basic.js:
* stress/big-int-literals.js:
Update tests to reflect new SyntaxErrors.

* ChakraCore.yaml:
* ChakraCore/test/EH/try6.baseline-jsc:
* ChakraCore/test/Error/variousErrors3.baseline-jsc: Added.
Update baselines to reflect new SyntaxErrors.

Source/JavaScriptCore:

Since ES6, expressions like 0++, ++0, 0 = 0, and 0 += 0 are all specified as early errors:
  https://tc39.github.io/ecma262/#sec-update-expressions-static-semantics-early-errors
  https://tc39.github.io/ecma262/#sec-assignment-operators-static-semantics-early-errors

We currently throw late ReferenceErrors for these -- let's turn them into early SyntaxErrors.
(This is based on the expectation that tc39/ecma262#1527 will be accepted;
if that doesn't come to pass, we can subsequently introduce early ReferenceError and revise these.)

* bytecompiler/NodesCodegen.cpp:
(JSC::PostfixNode::emitBytecode): Add an assert for "function call LHS" case.
(JSC::PrefixNode::emitBytecode): Add an assert for "function call LHS" case.

* parser/ASTBuilder.h:
(JSC::ASTBuilder::isLocation): Added.
(JSC::ASTBuilder::isAssignmentLocation): Fix misleading parameter name.
(JSC::ASTBuilder::isFunctionCall): Added.
(JSC::ASTBuilder::makeAssignNode): Add an assert for "function call LHS" case.
* parser/SyntaxChecker.h:
(JSC::SyntaxChecker::isLocation): Added.
(JSC::SyntaxChecker::isAssignmentLocation): Fix incorrect definition and align with ASTBuilder.
(JSC::SyntaxChecker::isFunctionCall): Added.
* parser/Nodes.h:
(JSC::ExpressionNode::isFunctionCall const): Added.
Ensure that the parser can check whether an expression node is a function call.

* parser/Parser.cpp:
(JSC::Parser<LexerType>::isSimpleAssignmentTarget): Added.
(JSC::Parser<LexerType>::parseAssignmentExpression):
(JSC::Parser<LexerType>::parseUnaryExpression): See below.
* parser/Parser.h:
Throw SyntaxError whenever an assignment or update expression's target is invalid.
Unfortunately, it seems that web compatibility obliges us to exempt the "function call LHS" case in sloppy mode.
(tc39/ecma262#257 (comment))

Additional cleanup items:
  - Make use of `semanticFailIfTrue` for `isMetaProperty` checks, as it's equivalent.
  - Rename `requiresLExpr` to `hasPrefixUpdateOp` since it's now confusing,
    and get rid of `modifiesExpr` since it refers to the exact same condition.
  - Stop setting `lastOperator` near the end -- one case was incorrect and regardless neither is used.

LayoutTests:

* fast/events/window-onerror4-expected.txt:
* ietestcenter/Javascript/11.13.1-1-1-expected.txt:
* ietestcenter/Javascript/11.13.1-1-2-expected.txt:
* ietestcenter/Javascript/11.13.1-1-3-expected.txt:
* ietestcenter/Javascript/11.13.1-1-4-expected.txt:
* js/basic-strict-mode-expected.txt:
* js/dom/assign-expected.txt:
* js/dom/line-column-numbers-expected.txt:
* js/dom/line-column-numbers.html:
* js/dom/postfix-syntax-expected.txt:
* js/dom/prefix-syntax-expected.txt:
* js/dom/script-tests/line-column-numbers.js:
* js/function-toString-parentheses-expected.txt:
* js/parser-syntax-check-expected.txt:
* js/parser-xml-close-comment-expected.txt:
* js/script-tests/function-toString-parentheses.js:
* js/script-tests/parser-syntax-check.js:
Update tests & expectations to reflect new SyntaxErrors.

* js/script-tests/toString-prefix-postfix-preserve-parens.js:
* js/toString-prefix-postfix-preserve-parens-expected.txt:
None of the prefix/postfix tests make sense here now that they're all SyntaxErrors;
remove them and just leave the typeof tests.


git-svn-id: http://svn.webkit.org/repository/webkit/trunk@245406 268f45cc-cd09-0410-ab3c-d52691b4dbfc
@ljharb
Copy link
Member

ljharb commented Jun 17, 2019

@littledan @bakkot what were the results of the v8 data collection?

@bakkot
Copy link
Contributor

bakkot commented Jun 17, 2019

@ljharb Use is very low in strict mode and less low in sloppy mode. Note that Spidermonkey and (as of recently, thanks @rkirsling) JSC only allow it in sloppy mode, so forbidding it in strict mode is very likely to be web compatible.

I can't make the call for what browsers would be comfortable shipping, but eyeballing the above graphs (and given that every browser has found they need to allow this syntax in sloppy mode), I think it makes to spec "f() = 0 is a runtime {syntax/reference} error in sloppy mode, early syntax error in strict" (details TBD) at this point.

ryanhaddad pushed a commit to WebKit/WebKit that referenced this issue Dec 22, 2020
https://bugs.webkit.org/show_bug.cgi?id=209716

Reviewed by Ross Kirsling.

JSTests:

* stress/logical-assignment-operator-and.js: Added.
* stress/logical-assignment-operator-nullish.js: Added.
* stress/logical-assignment-operator-or.js: Added.

* test262/config.yaml:
* test262/expectations.yaml:
Right now, test262 expects an early error to be thrown if the lhs is not simple, which does
not match what we do with other read-modify assignment operators. This is likely to change
in the future to match existing behavior (throw a `ReferenceError`) [1].

[1]: <tc39/ecma262#257 (comment)>

Source/JavaScriptCore:

Implement the logical assignment operators proposal, which is now Stage 3. It introduces
three new assignment operators which will only store the result of the rhs in the lhs if the
lhs meets the given condition:
 - `??=`, for if the lhs is nullish (`null` or `undefined`)
 - `||=`, for if the lhs is falsy
 - `&&=`, for if the lhs is truthy

This short circuiting can be beneficial as it can avoid a redundant store when used in the
common JavaScript programming pattern of "defaulting" a parameter.

```js
    function foo(x) {
        x = x || 42;
    }
```

If `x` is a truthy value, it would result in the rhs `x` being stored back into the lhs `x`.
In some situations, this can have negative unintended side-effects, such as for `innerHTML`.

Logical assignment operators, however, are defined such that they only store if the rhs is
to actually be needed/used, skipping the redundant store and simply returning lhs otherwise.

In the case of readonly references, this means that an error is only thrown when the
assignment occurs, meaning that if the lhs already satisfies the condition it will be used
and returned with no error.

* parser/ParserTokens.h:
* parser/Lexer.cpp:
(JSC::Lexer<T>::lexWithoutClearingLineTerminator):
* parser/Parser.cpp:
(JSC::Parser<LexerType>::parseAssignmentExpression):

* parser/ASTBuilder.h:
(JSC::ASTBuilder::makeAssignNode):
* parser/Nodes.h:
* parser/NodeConstructors.h:
(JSC::ShortCircuitReadModifyResolveNode::ShortCircuitReadModifyResolveNode): Added.
(JSC::ShortCircuitReadModifyBracketNode::ShortCircuitReadModifyBracketNode): Added.
(JSC::ShortCircuitReadModifyDotNode::ShortCircuitReadModifyDotNode): Added.
* bytecompiler/NodesCodegen.cpp:
(JSC::emitShortCircuitAssignment): Added.
(JSC::ShortCircuitReadModifyResolveNode::emitBytecode): Added.
(JSC::ShortCircuitReadModifyDotNode::emitBytecode): Added.
(JSC::ShortCircuitReadModifyBracketNode::emitBytecode): Added.

* runtime/OptionsList.h:
Add a `useLogicalAssignmentOperators` setting for controlling this feature.

Tools:

* Scripts/run-jsc-stress-tests:


Canonical link: https://commits.webkit.org/223412@main
git-svn-id: https://svn.webkit.org/repository/webkit/trunk@260119 268f45cc-cd09-0410-ab3c-d52691b4dbfc
@bakkot
Copy link
Contributor

bakkot commented Oct 26, 2021

For posterity, I believe the historical use of this syntax was for vbarrays: https://matrixlogs.bakkot.com/TC39_Delegates/2021-10-26#L443-L450

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

10 participants