Bug 1414805 - Don't mishandle ASI after a ternary expression ending in an arrow function with block body, or such ternary appearing in a computed property name expression. r=arai
authorJeff Walden <jwalden@mit.edu>
Tue, 07 Nov 2017 16:03:52 -0800
changeset 444107 8b99335365c576e55eed47d7b85b99efb73afac7
parent 444106 50b8043a629c482ce42ff9ac29fb761bd055008a
child 444108 d8441c69c0831e7b497991ff20c8fb09506afd9b
push id1618
push userCallek@gmail.com
push dateThu, 11 Jan 2018 17:45:48 +0000
treeherdermozilla-release@882ca853e05a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersarai
bugs1414805
milestone58.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1414805 - Don't mishandle ASI after a ternary expression ending in an arrow function with block body, or such ternary appearing in a computed property name expression. r=arai
js/src/frontend/Parser.cpp
js/src/tests/ecma_6/ArrowFunctions/arrow-not-as-end-of-statement.js
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -4438,18 +4438,20 @@ Parser<ParseHandler, CharT>::objectBindi
         return null();
 
     Maybe<DeclarationKind> declKind = Some(kind);
     RootedAtom propAtom(context);
     for (;;) {
         TokenKind tt;
         if (!tokenStream.peekToken(&tt))
             return null();
-        if (tt == TOK_RC)
+        if (tt == TOK_RC) {
+            tokenStream.addModifierException(TokenStream::OperandIsNone);
             break;
+        }
 
         if (tt == TOK_TRIPLEDOT) {
             tokenStream.consumeKnownToken(TOK_TRIPLEDOT);
             uint32_t begin = pos().begin;
 
             TokenKind tt;
             if (!tokenStream.getToken(&tt))
                 return null();
@@ -4479,17 +4481,17 @@ Parser<ParseHandler, CharT>::objectBindi
                 if (!tokenStream.getToken(&tt, TokenStream::Operand))
                     return null();
 
                 Node binding = bindingIdentifierOrPattern(kind, yieldHandling, tt);
                 if (!binding)
                     return null();
 
                 bool hasInitializer;
-                if (!tokenStream.matchToken(&hasInitializer, TOK_ASSIGN))
+                if (!tokenStream.matchToken(&hasInitializer, TOK_ASSIGN, TokenStream::Operand))
                     return null();
 
                 Node bindingExpr = hasInitializer
                                    ? bindingInitializer(binding, kind, yieldHandling)
                                    : binding;
                 if (!bindingExpr)
                     return null();
 
@@ -4525,27 +4527,27 @@ Parser<ParseHandler, CharT>::objectBindi
                     return null();
             } else {
                 errorAt(namePos.begin, JSMSG_NO_VARIABLE_NAME);
                 return null();
             }
         }
 
         bool matched;
-        if (!tokenStream.matchToken(&matched, TOK_COMMA))
+        if (!tokenStream.matchToken(&matched, TOK_COMMA, TokenStream::Operand))
             return null();
         if (!matched)
             break;
         if (tt == TOK_TRIPLEDOT) {
             error(JSMSG_REST_WITH_COMMA);
             return null();
         }
     }
 
-    MUST_MATCH_TOKEN_MOD_WITH_REPORT(TOK_RC, TokenStream::None,
+    MUST_MATCH_TOKEN_MOD_WITH_REPORT(TOK_RC, TokenStream::Operand,
                                      reportMissingClosing(JSMSG_CURLY_AFTER_LIST,
                                                           JSMSG_CURLY_OPENED, begin));
 
     handler.setEndPosition(literal, pos().end);
     return literal;
 }
 
 template <class ParseHandler, typename CharT>
@@ -4570,16 +4572,17 @@ Parser<ParseHandler, CharT>::arrayBindin
          }
 
          TokenKind tt;
          if (!tokenStream.getToken(&tt))
              return null();
 
          if (tt == TOK_RB) {
              tokenStream.ungetToken();
+             tokenStream.addModifierException(TokenStream::OperandIsNone);
              break;
          }
 
          if (tt == TOK_COMMA) {
              if (!handler.addElision(literal, pos()))
                  return null();
          } else if (tt == TOK_TRIPLEDOT) {
              uint32_t begin = pos().begin;
@@ -4595,44 +4598,44 @@ Parser<ParseHandler, CharT>::arrayBindin
              if (!handler.addSpreadElement(literal, begin, inner))
                  return null();
          } else {
              Node binding = bindingIdentifierOrPattern(kind, yieldHandling, tt);
              if (!binding)
                  return null();
 
              bool hasInitializer;
-             if (!tokenStream.matchToken(&hasInitializer, TOK_ASSIGN))
+             if (!tokenStream.matchToken(&hasInitializer, TOK_ASSIGN, TokenStream::Operand))
                  return null();
 
              Node element = hasInitializer
                             ? bindingInitializer(binding, kind, yieldHandling)
                             : binding;
              if (!element)
                  return null();
 
              handler.addArrayElement(literal, element);
          }
 
          if (tt != TOK_COMMA) {
              // If we didn't already match TOK_COMMA in above case.
              bool matched;
-             if (!tokenStream.matchToken(&matched, TOK_COMMA))
+             if (!tokenStream.matchToken(&matched, TOK_COMMA, TokenStream::Operand))
                  return null();
              if (!matched)
                  break;
 
              if (tt == TOK_TRIPLEDOT) {
                  error(JSMSG_REST_WITH_COMMA);
                  return null();
              }
          }
      }
 
-     MUST_MATCH_TOKEN_MOD_WITH_REPORT(TOK_RB, TokenStream::None,
+     MUST_MATCH_TOKEN_MOD_WITH_REPORT(TOK_RB, TokenStream::Operand,
                                       reportMissingClosing(JSMSG_BRACKET_AFTER_LIST,
                                                            JSMSG_BRACKET_OPENED, begin));
 
     handler.setEndPosition(literal, pos().end);
     return literal;
 }
 
 template <class ParseHandler, typename CharT>
@@ -8016,77 +8019,57 @@ Parser<ParseHandler, CharT>::assignExpr(
     // Save the tokenizer state in case we find an arrow function and have to
     // rewind.
     TokenStream::Position start(keepAtoms);
     tokenStream.tell(&start);
 
     PossibleError possibleErrorInner(*this);
     Node lhs;
     TokenKind tokenAfterLHS;
+    bool isArrow;
     if (maybeAsyncArrow) {
         tokenStream.consumeKnownToken(TOK_ASYNC, TokenStream::Operand);
 
         TokenKind tokenAfterAsync;
         if (!tokenStream.getToken(&tokenAfterAsync))
             return null();
         MOZ_ASSERT(TokenKindIsPossibleIdentifier(tokenAfterAsync));
 
         // Check yield validity here.
         RootedPropertyName name(context, bindingIdentifier(yieldHandling));
         if (!name)
             return null();
 
-        if (!tokenStream.getToken(&tokenAfterLHS))
+        if (!tokenStream.peekTokenSameLine(&tokenAfterLHS))
             return null();
         if (tokenAfterLHS != TOK_ARROW) {
             error(JSMSG_UNEXPECTED_TOKEN,
-                  "'=>' after argument list", TokenKindToDesc(tokenAfterLHS));
-            return null();
-        }
+                  "'=>' on the same line after an argument list", TokenKindToDesc(tokenAfterLHS));
+            return null();
+        }
+
+        isArrow = true;
     } else {
         lhs = condExpr(inHandling, yieldHandling, tripledotHandling, &possibleErrorInner, invoked);
         if (!lhs)
             return null();
 
-        if (!tokenStream.getToken(&tokenAfterLHS))
-            return null();
-    }
-
-    ParseNodeKind kind;
-    switch (tokenAfterLHS) {
-      case TOK_ASSIGN:       kind = PNK_ASSIGN;       break;
-      case TOK_ADDASSIGN:    kind = PNK_ADDASSIGN;    break;
-      case TOK_SUBASSIGN:    kind = PNK_SUBASSIGN;    break;
-      case TOK_BITORASSIGN:  kind = PNK_BITORASSIGN;  break;
-      case TOK_BITXORASSIGN: kind = PNK_BITXORASSIGN; break;
-      case TOK_BITANDASSIGN: kind = PNK_BITANDASSIGN; break;
-      case TOK_LSHASSIGN:    kind = PNK_LSHASSIGN;    break;
-      case TOK_RSHASSIGN:    kind = PNK_RSHASSIGN;    break;
-      case TOK_URSHASSIGN:   kind = PNK_URSHASSIGN;   break;
-      case TOK_MULASSIGN:    kind = PNK_MULASSIGN;    break;
-      case TOK_DIVASSIGN:    kind = PNK_DIVASSIGN;    break;
-      case TOK_MODASSIGN:    kind = PNK_MODASSIGN;    break;
-      case TOK_POWASSIGN:    kind = PNK_POWASSIGN;    break;
-
-      case TOK_ARROW: {
-
-        // A line terminator between ArrowParameters and the => should trigger a SyntaxError.
-        tokenStream.ungetToken();
+        // Use Operand here because the ConditionalExpression parsed above
+        // could be the entirety of this AssignmentExpression, and then ASI
+        // permits this token to be a regular expression.
+        if (!tokenStream.peekTokenSameLine(&tokenAfterLHS, TokenStream::Operand))
+            return null();
+
+        isArrow = tokenAfterLHS == TOK_ARROW;
+    }
+
+    if (isArrow) {
+        tokenStream.seek(start);
+
         TokenKind next;
-        if (!tokenStream.peekTokenSameLine(&next))
-            return null();
-        MOZ_ASSERT(next == TOK_ARROW || next == TOK_EOL);
-
-        if (next != TOK_ARROW) {
-            error(JSMSG_LINE_BREAK_BEFORE_ARROW);
-            return null();
-        }
-
-        tokenStream.seek(start);
-
         if (!tokenStream.getToken(&next, TokenStream::Operand))
             return null();
         uint32_t toStringStart = pos().begin;
         tokenStream.ungetToken();
 
         FunctionAsyncKind asyncKind = SyncFunction;
 
         if (next == TOK_ASYNC) {
@@ -8106,29 +8089,46 @@ Parser<ParseHandler, CharT>::assignExpr(
         }
 
         Node pn = handler.newArrowFunction(pos());
         if (!pn)
             return null();
 
         return functionDefinition(pn, toStringStart, inHandling, yieldHandling, nullptr,
                                   Arrow, GeneratorKind::NotGenerator, asyncKind);
-      }
+    }
+
+    MOZ_ALWAYS_TRUE(tokenStream.getToken(&tokenAfterLHS, TokenStream::Operand));
+
+    ParseNodeKind kind;
+    switch (tokenAfterLHS) {
+      case TOK_ASSIGN:       kind = PNK_ASSIGN;       break;
+      case TOK_ADDASSIGN:    kind = PNK_ADDASSIGN;    break;
+      case TOK_SUBASSIGN:    kind = PNK_SUBASSIGN;    break;
+      case TOK_BITORASSIGN:  kind = PNK_BITORASSIGN;  break;
+      case TOK_BITXORASSIGN: kind = PNK_BITXORASSIGN; break;
+      case TOK_BITANDASSIGN: kind = PNK_BITANDASSIGN; break;
+      case TOK_LSHASSIGN:    kind = PNK_LSHASSIGN;    break;
+      case TOK_RSHASSIGN:    kind = PNK_RSHASSIGN;    break;
+      case TOK_URSHASSIGN:   kind = PNK_URSHASSIGN;   break;
+      case TOK_MULASSIGN:    kind = PNK_MULASSIGN;    break;
+      case TOK_DIVASSIGN:    kind = PNK_DIVASSIGN;    break;
+      case TOK_MODASSIGN:    kind = PNK_MODASSIGN;    break;
+      case TOK_POWASSIGN:    kind = PNK_POWASSIGN;    break;
 
       default:
         MOZ_ASSERT(!tokenStream.isCurrentTokenAssignment());
         if (!possibleError) {
             if (!possibleErrorInner.checkForExpressionError())
                 return null();
         } else {
             possibleErrorInner.transferErrorsTo(possibleError);
         }
 
         tokenStream.ungetToken();
-        tokenStream.addModifierException(TokenStream::OperandIsNone);
         return lhs;
     }
 
     // Verify the left-hand side expression doesn't have a forbidden form.
     if (handler.isUnparenthesizedDestructuringPattern(lhs)) {
         if (kind != PNK_ASSIGN) {
             error(JSMSG_BAD_DESTRUCT_ASS);
             return null();
@@ -8706,31 +8706,29 @@ Parser<ParseHandler, CharT>::argumentLis
             argNode = handler.newSpread(begin, argNode);
             if (!argNode)
                 return false;
         }
 
         handler.addList(listNode, argNode);
 
         bool matched;
-        if (!tokenStream.matchToken(&matched, TOK_COMMA))
+        if (!tokenStream.matchToken(&matched, TOK_COMMA, TokenStream::Operand))
             return false;
         if (!matched)
             break;
 
         TokenKind tt;
         if (!tokenStream.peekToken(&tt, TokenStream::Operand))
             return null();
-        if (tt == TOK_RP) {
-            tokenStream.addModifierException(TokenStream::NoneIsOperand);
+        if (tt == TOK_RP)
             break;
-        }
-    }
-
-    MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_ARGS);
+    }
+
+    MUST_MATCH_TOKEN_MOD(TOK_RP, TokenStream::Operand, JSMSG_PAREN_AFTER_ARGS);
 
     handler.setEndPosition(listNode, pos().end);
     return true;
 }
 
 template <class ParseHandler, typename CharT>
 bool
 Parser<ParseHandler, CharT>::checkAndMarkSuperScope()
@@ -9588,25 +9586,28 @@ Parser<ParseHandler, CharT>::propertyNam
 
     if (TokenKindIsPossibleIdentifierName(ltok) &&
         (tt == TOK_COMMA || tt == TOK_RC || tt == TOK_ASSIGN))
     {
         if (isGenerator || isAsync) {
             error(JSMSG_BAD_PROP_ID);
             return null();
         }
+
         tokenStream.ungetToken();
-        *propType = tt == TOK_ASSIGN ?
-                          PropertyType::CoverInitializedName :
-                          PropertyType::Shorthand;
+        tokenStream.addModifierException(TokenStream::OperandIsNone);
+        *propType = tt == TOK_ASSIGN
+                    ? PropertyType::CoverInitializedName
+                    : PropertyType::Shorthand;
         return propName;
     }
 
     if (tt == TOK_LP) {
         tokenStream.ungetToken();
+
         if (isGenerator && isAsync)
             *propType = PropertyType::AsyncGeneratorMethod;
         else if (isGenerator)
             *propType = PropertyType::GeneratorMethod;
         else if (isAsync)
             *propType = PropertyType::AsyncMethod;
         else
             *propType = PropertyType::Method;
@@ -9633,17 +9634,17 @@ Parser<ParseHandler, CharT>::computedPro
     } else {
         handler.setListFlag(literal, PNX_NONCONST);
     }
 
     Node assignNode = assignExpr(InAllowed, yieldHandling, TripledotProhibited);
     if (!assignNode)
         return null();
 
-    MUST_MATCH_TOKEN(TOK_RB, JSMSG_COMP_PROP_UNTERM_EXPR);
+    MUST_MATCH_TOKEN_MOD(TOK_RB, TokenStream::Operand, JSMSG_COMP_PROP_UNTERM_EXPR);
     return handler.newComputedName(assignNode, begin, pos().end);
 }
 
 template <class ParseHandler, typename CharT>
 typename ParseHandler::Node
 Parser<ParseHandler, CharT>::objectLiteral(YieldHandling yieldHandling,
                                            PossibleError* possibleError)
 {
@@ -9658,18 +9659,20 @@ Parser<ParseHandler, CharT>::objectLiter
     bool seenPrototypeMutation = false;
     bool seenCoverInitializedName = false;
     Maybe<DeclarationKind> declKind = Nothing();
     RootedAtom propAtom(context);
     for (;;) {
         TokenKind tt;
         if (!tokenStream.peekToken(&tt))
             return null();
-        if (tt == TOK_RC)
+        if (tt == TOK_RC) {
+            tokenStream.addModifierException(TokenStream::OperandIsNone);
             break;
+        }
 
         if (tt == TOK_TRIPLEDOT) {
             tokenStream.consumeKnownToken(TOK_TRIPLEDOT);
             uint32_t begin = pos().begin;
 
             TokenPos innerPos;
             if (!tokenStream.peekTokenPos(&innerPos, TokenStream::Operand))
                 return null();
@@ -9845,25 +9848,25 @@ Parser<ParseHandler, CharT>::objectLiter
                 if (possibleError) {
                     possibleError->setPendingDestructuringErrorAt(namePos,
                                                                   JSMSG_BAD_DESTRUCT_TARGET);
                 }
             }
         }
 
         bool matched;
-        if (!tokenStream.matchToken(&matched, TOK_COMMA))
+        if (!tokenStream.matchToken(&matched, TOK_COMMA, TokenStream::Operand))
             return null();
         if (!matched)
             break;
         if (tt == TOK_TRIPLEDOT && possibleError)
             possibleError->setPendingDestructuringErrorAt(pos(), JSMSG_REST_WITH_COMMA);
     }
 
-    MUST_MATCH_TOKEN_MOD_WITH_REPORT(TOK_RC, TokenStream::None,
+    MUST_MATCH_TOKEN_MOD_WITH_REPORT(TOK_RC, TokenStream::Operand,
                                      reportMissingClosing(JSMSG_CURLY_AFTER_LIST,
                                                           JSMSG_CURLY_OPENED, openedPos));
 
     handler.setEndPosition(literal, pos().end);
     return literal;
 }
 
 template <class ParseHandler, typename CharT>
--- a/js/src/tests/ecma_6/ArrowFunctions/arrow-not-as-end-of-statement.js
+++ b/js/src/tests/ecma_6/ArrowFunctions/arrow-not-as-end-of-statement.js
@@ -52,16 +52,57 @@ switch (a => {})
 
 with (a => {});
 
 assertEq(typeof (a => {}), "function");
 
 for (var x in y => {})
   continue;
 
+assertEq(eval("a => {}, 17, 42;"), 42);
+assertEq(eval("42, a => {}, 17;"), 17);
+assertEq(typeof eval("17, 42, a => {};"), "function");
+
+assertEq(eval("1 ? 0 : a => {}, 17, 42;"), 42);
+assertEq(eval("42, 1 ? 0 : a => {}, 17;"), 17);
+assertEq(eval("17, 42, 1 ? 0 : a => {};"), 0);
+
 var z = { x: 0 ? 1 : async a => {} };
 assertEq(typeof z.x, "function");
 
 var q = 0 ? 1 : async () => {};
 assertEq(typeof q, "function");
 
+var m = 0 ? 42 : m = foo => {} // ASI
+/Q/g;
+assertEq(typeof m, "function");
+
+var { q: w = 0 ? 1 : a => {} } = {};
+assertEq(typeof w, "function");
+
+Function.prototype.c = 42;
+var { c } = 0 ? 1 : a => {} // ASI
+/Q/g;
+assertEq(c, 42);
+
+var c = 0 ? 1 : a => {}
+/Q/g;
+assertEq(typeof c, "function");
+delete Function.prototype.c;
+
+assertEq(typeof eval(0 ? 1 : a => {}), "function");
+
+var zoom = 1 ? a => {} : 357;
+assertEq(typeof zoom, "function");
+
+var { b = 0 ? 1 : a => {} } = {};
+assertEq(typeof b, "function");
+
+var [k = 0 ? 1 : a => {}] = [];
+assertEq(typeof k, "function");
+
+assertEq(typeof [0 ? 1 : a => {}][0], "function");
+
+var { [0 ? 1 : a => {}]: h } = { "a => {}": "boo-urns!" };
+assertEq(h, "boo-urns!");
+
 if (typeof reportCompare === "function")
   reportCompare(true, true);