Bug 1596706. r=tcampbell a=RyanVM FENNEC_68_5_0_BUILD1 FIREFOX_68_5_0esr_BUILD1
authorJeff Walden <jwalden@mit.edu>
Fri, 24 Jan 2020 12:04:14 +0000 (2020-01-24)
changeset 524515 18251ad24c9a3b89c42d4098b64e71f54a1e4586
parent 524514 6168a78b9dd7cd1ea26eb52c362e72ad93572eaf
child 524516 68114ccd40aa31b83eaa404b98ec59ea16511a21
push id833
push userryanvm@gmail.com
push dateMon, 03 Feb 2020 19:41:12 +0000 (2020-02-03)
treeherdermozilla-esr68@18251ad24c9a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstcampbell, RyanVM
bugs1596706
milestone68.5.0
Bug 1596706. r=tcampbell a=RyanVM Differential Revision: https://phabricator.services.mozilla.com/D59963
js/src/frontend/BytecodeCompiler.cpp
js/src/frontend/Parser.cpp
js/src/frontend/Parser.h
js/src/frontend/TokenStream.cpp
js/src/frontend/TokenStream.h
--- a/js/src/frontend/BytecodeCompiler.cpp
+++ b/js/src/frontend/BytecodeCompiler.cpp
@@ -478,17 +478,18 @@ bool frontend::SourceAwareCompiler<Unit>
     // Hit some unrecoverable ambiguity during an inner syntax parse.
     // Syntax parsing has now been disabled in the parser, so retry
     // the parse.
     parser->clearAbortedSyntaxParse();
   } else if (parser->anyChars.hadError() || info.directives == newDirectives) {
     return false;
   }
 
-  parser->tokenStream.seek(startPosition);
+  // Rewind to starting position to retry.
+  parser->tokenStream.rewind(startPosition);
 
   // Assignment must be monotonic to prevent reparsing iloops
   MOZ_ASSERT_IF(info.directives.strict(), newDirectives.strict());
   MOZ_ASSERT_IF(info.directives.asmJS(), newDirectives.asmJS());
   info.directives = newDirectives;
   return true;
 }
 
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -2619,27 +2619,44 @@ GeneralParser<ParseHandler, Unit>::funct
     }
 
     // Assignment must be monotonic to prevent infinitely attempting to
     // reparse.
     MOZ_ASSERT_IF(directives.strict(), newDirectives.strict());
     MOZ_ASSERT_IF(directives.asmJS(), newDirectives.asmJS());
     directives = newDirectives;
 
-    tokenStream.seek(start);
+    // Rewind to retry parsing with new directives applied.
+    tokenStream.rewind(start);
 
     // functionFormalParametersAndBody may have already set body before
     // failing.
     handler_.setFunctionFormalParametersAndBody(funNode, null());
   }
 
   return funNode;
 }
 
 template <typename Unit>
+bool Parser<FullParseHandler, Unit>::advancePastSyntaxParsedFunction(
+    AutoKeepAtoms& keepAtoms, SyntaxParser* syntaxParser) {
+  MOZ_ASSERT(getSyntaxParser() == syntaxParser);
+
+  // Advance this parser over tokens processed by the syntax parser.
+  Position currentSyntaxPosition(keepAtoms_, syntaxParser->tokenStream);
+  if (!tokenStream.fastForward(currentSyntaxPosition, syntaxParser->anyChars)) {
+    return false;
+  }
+
+  anyChars.adoptState(syntaxParser->anyChars);
+  tokenStream.adoptState(syntaxParser->tokenStream);
+  return true;
+}
+
+template <typename Unit>
 bool Parser<FullParseHandler, Unit>::trySyntaxParseInnerFunction(
     FunctionNode** funNode, HandleFunction fun, uint32_t toStringStart,
     InHandling inHandling, YieldHandling yieldHandling, FunctionSyntaxKind kind,
     GeneratorKind generatorKind, FunctionAsyncKind asyncKind, bool tryAnnexB,
     Directives inheritedDirectives, Directives* newDirectives) {
   // Try a syntax parse for this inner function.
   do {
     // If we're assuming this function is an IIFE, always perform a full
@@ -2654,19 +2671,27 @@ bool Parser<FullParseHandler, Unit>::try
 
     SyntaxParser* syntaxParser = getSyntaxParser();
     if (!syntaxParser) {
       break;
     }
 
     UsedNameTracker::RewindToken token = usedNames_.getRewindToken();
 
-    // Move the syntax parser to the current position in the stream.
+    // Move the syntax parser to the current position in the stream.  In the
+    // common case this seeks forward, but it'll also seek backward *at least*
+    // when arrow functions appear inside arrow function argument defaults
+    // (because we rewind to reparse arrow functions once we're certain they're
+    // arrow functions):
+    //
+    //   var x = (y = z => 2) => q;
+    //   //           ^ we first seek to here to syntax-parse this function
+    //   //      ^ then we seek back to here to syntax-parse the outer function
     Position currentPosition(keepAtoms_, tokenStream);
-    if (!syntaxParser->tokenStream.seek(currentPosition, anyChars)) {
+    if (!syntaxParser->tokenStream.seekTo(currentPosition, anyChars)) {
       return false;
     }
 
     // Make a FunctionBox before we enter the syntax parser, because |pn|
     // still expects a FunctionBox to be attached to it during BCE, and
     // the syntax parser cannot attach one to it.
     FunctionBox* funbox =
         newFunctionBox(*funNode, fun, toStringStart, inheritedDirectives,
@@ -2689,19 +2714,17 @@ bool Parser<FullParseHandler, Unit>::try
         usedNames_.rewind(token);
         MOZ_ASSERT_IF(!syntaxParser->cx_->helperThread(),
                       !syntaxParser->cx_->isExceptionPending());
         break;
       }
       return false;
     }
 
-    // Advance this parser over tokens processed by the syntax parser.
-    Position currentSyntaxPosition(keepAtoms_, syntaxParser->tokenStream);
-    if (!tokenStream.seek(currentSyntaxPosition, syntaxParser->anyChars)) {
+    if (!advancePastSyntaxParsedFunction(keepAtoms_, syntaxParser)) {
       return false;
     }
 
     // Update the end position of the parse node.
     (*funNode)->pn_pos.end = anyChars.currentToken().pos.end;
 
     // Append possible Annex B function box only upon successfully parsing.
     if (tryAnnexB) {
@@ -8377,17 +8400,18 @@ typename ParseHandler::Node GeneralParse
                                        TokenStream::SlashIsRegExp)) {
       return null();
     }
 
     isArrow = tokenAfterLHS == TokenKind::Arrow;
   }
 
   if (isArrow) {
-    tokenStream.seek(start);
+    // Rewind to reparse as an arrow function.
+    tokenStream.rewind(start);
 
     TokenKind next;
     if (!tokenStream.getToken(&next, TokenStream::SlashIsRegExp)) {
       return null();
     }
     TokenPos startPos = pos();
     uint32_t toStringStart = startPos.begin;
     anyChars.ungetToken();
--- a/js/src/frontend/Parser.h
+++ b/js/src/frontend/Parser.h
@@ -1737,16 +1737,19 @@ class MOZ_STACK_CLASS Parser<FullParseHa
 
   bool trySyntaxParseInnerFunction(
       FunctionNodeType* funNode, HandleFunction fun, uint32_t toStringStart,
       InHandling inHandling, YieldHandling yieldHandling,
       FunctionSyntaxKind kind, GeneratorKind generatorKind,
       FunctionAsyncKind asyncKind, bool tryAnnexB,
       Directives inheritedDirectives, Directives* newDirectives);
 
+  MOZ_MUST_USE bool advancePastSyntaxParsedFunction(AutoKeepAtoms& keepAtoms,
+                                                    SyntaxParser* syntaxParser);
+
   bool skipLazyInnerFunction(FunctionNodeType funNode, uint32_t toStringStart,
                              FunctionSyntaxKind kind, bool tryAnnexB);
 
   // Functions present only in Parser<FullParseHandler, Unit>.
 
   // Parse the body of an eval.
   //
   // Eval scripts are distinguished from global scripts in that in ES6, per
--- a/js/src/frontend/TokenStream.cpp
+++ b/js/src/frontend/TokenStream.cpp
@@ -1553,17 +1553,17 @@ bool TokenStreamSpecific<Unit, AnyCharsA
   cur->pos.begin = this->sourceUnits.offset();
   cur->pos.end = cur->pos.begin;
   MOZ_MAKE_MEM_UNDEFINED(&cur->type, sizeof(cur->type));
   anyChars.lookahead = 0;
   return true;
 }
 
 template <typename Unit, class AnyCharsAccess>
-void TokenStreamSpecific<Unit, AnyCharsAccess>::seek(const Position& pos) {
+void TokenStreamSpecific<Unit, AnyCharsAccess>::seekTo(const Position& pos) {
   TokenStreamAnyChars& anyChars = anyCharsAccess();
 
   this->sourceUnits.setAddressOfNextCodeUnit(pos.buf,
                                              /* allowPoisoned = */ true);
   anyChars.flags = pos.flags;
   anyChars.lineno = pos.lineno;
   anyChars.linebase = pos.linebase;
   anyChars.prevLinebase = pos.prevLinebase;
@@ -1571,23 +1571,23 @@ void TokenStreamSpecific<Unit, AnyCharsA
 
   anyChars.tokens[anyChars.cursor()] = pos.currentToken;
   for (unsigned i = 0; i < anyChars.lookahead; i++) {
     anyChars.tokens[anyChars.aheadCursor(1 + i)] = pos.lookaheadTokens[i];
   }
 }
 
 template <typename Unit, class AnyCharsAccess>
-bool TokenStreamSpecific<Unit, AnyCharsAccess>::seek(
+bool TokenStreamSpecific<Unit, AnyCharsAccess>::seekTo(
     const Position& pos, const TokenStreamAnyChars& other) {
   if (!anyCharsAccess().srcCoords.fill(other.srcCoords)) {
     return false;
   }
 
-  seek(pos);
+  seekTo(pos);
   return true;
 }
 
 void TokenStreamAnyChars::computeErrorMetadataNoOffset(ErrorMetadata* err) {
   err->isMuted = mutedErrors;
   err->filename = filename_;
   err->lineNumber = 0;
   err->columnNumber = 0;
--- a/js/src/frontend/TokenStream.h
+++ b/js/src/frontend/TokenStream.h
@@ -730,16 +730,29 @@ class TokenStreamAnyChars : public Token
   // Push the last scanned token back into the stream.
   void ungetToken() {
     MOZ_ASSERT(lookahead < maxLookahead);
     lookahead++;
     retractCursor();
   }
 
  public:
+  void adoptState(TokenStreamAnyChars& other) {
+    // If |other| has fresh information from directives, overwrite any
+    // previously recorded directives.  (There is no specification directing
+    // that last-in-source-order directive controls, sadly.  We behave this way
+    // in the ordinary case, so we ought do so here too.)
+    if (auto& url = other.displayURL_) {
+      displayURL_ = std::move(url);
+    }
+    if (auto& url = other.sourceMapURL_) {
+      sourceMapURL_ = std::move(url);
+    }
+  }
+
   // Compute error metadata for an error at no offset.
   void computeErrorMetadataNoOffset(ErrorMetadata* err);
 
   // ErrorReporter API Helpers
 
   // Provide minimal set of error reporting API given we cannot use
   // ErrorReportMixin here. "report" prefix is added to avoid conflict with
   // ErrorReportMixin methods in TokenStream class.
@@ -1422,16 +1435,24 @@ class TokenStreamCharsShared {
   }
 
   JSAtom* drainCharBufferIntoAtom(JSContext* cx) {
     JSAtom* atom = AtomizeChars(cx, charBuffer.begin(), charBuffer.length());
     charBuffer.clear();
     return atom;
   }
 
+ protected:
+  void adoptState(TokenStreamCharsShared& other) {
+    // The other stream's buffer may contain information for a
+    // gotten-then-ungotten token, that we must transfer into this stream so
+    // that token's final get behaves as desired.
+    charBuffer = std::move(other.charBuffer);
+  }
+
  public:
   CharBuffer& getCharBuffer() { return charBuffer; }
 };
 
 inline mozilla::Span<const char> ToCharSpan(
     mozilla::Span<const mozilla::Utf8Unit> codeUnits) {
   static_assert(alignof(char) == alignof(mozilla::Utf8Unit),
                 "must have equal alignment to reinterpret_cast<>");
@@ -1999,16 +2020,20 @@ class GeneralTokenStreamChars : public S
       end =
           this->sourceUnits.codeUnitPtrAt(anyChars.currentToken().pos.end - 2);
     } else {
       // NO_SUBS_TEMPLATE is of the form   |`...`|   or   |}...`|
       end =
           this->sourceUnits.codeUnitPtrAt(anyChars.currentToken().pos.end - 1);
     }
 
+    // |charBuffer| should be empty here, but we may as well code defensively.
+    MOZ_ASSERT(this->charBuffer.length() == 0);
+    this->charBuffer.clear();
+
     // Template literals normalize only '\r' and "\r\n" to '\n'; Unicode
     // separators don't need special handling.
     // https://tc39.github.io/ecma262/#sec-static-semantics-tv-and-trv
     if (!fillCharBufferFromSourceNormalizingAsciiLineBreaks(cur, end)) {
       return nullptr;
     }
 
     return drainCharBufferIntoAtom(anyChars.cx);
@@ -2281,16 +2306,17 @@ class MOZ_STACK_CLASS TokenStreamSpecifi
   //
   // As an alternative, we directly add every one of these functions to this
   // class, using explicit qualification to address the dependent-name
   // problem.  |this| or other qualification is no longer necessary -- at
   // cost of this ever-changing laundry list of |using|s.  So it goes.
  public:
   using GeneralCharsBase::anyCharsAccess;
   using GeneralCharsBase::computeLineAndColumn;
+  using TokenStreamCharsShared::adoptState;
 
  private:
   using typename CharsBase::SourceUnits;
 
  private:
   using CharsBase::atomizeSourceChars;
   using GeneralCharsBase::badToken;
   using TokenStreamCharsShared::appendCodePointToCharBuffer;
@@ -2641,18 +2667,45 @@ class MOZ_STACK_CLASS TokenStreamSpecifi
       // with SlashIsRegExp, so we have to do it manually here.
       anyCharsAccess().allowGettingNextTokenWithSlashIsRegExp();
     }
     return true;
   }
 
   MOZ_MUST_USE bool advance(size_t position);
 
-  void seek(const Position& pos);
-  MOZ_MUST_USE bool seek(const Position& pos, const TokenStreamAnyChars& other);
+  void seekTo(const Position& pos);
+  MOZ_MUST_USE bool seekTo(const Position& pos,
+                           const TokenStreamAnyChars& other);
+
+  void rewind(const Position& pos) {
+    MOZ_ASSERT(pos.buf <= this->sourceUnits.addressOfNextCodeUnit(),
+               "should be rewinding here");
+    seekTo(pos);
+  }
+
+  MOZ_MUST_USE bool rewind(const Position& pos,
+                           const TokenStreamAnyChars& other) {
+    MOZ_ASSERT(pos.buf <= this->sourceUnits.addressOfNextCodeUnit(),
+               "should be rewinding here");
+    return seekTo(pos, other);
+  }
+
+  void fastForward(const Position& pos) {
+    MOZ_ASSERT(this->sourceUnits.addressOfNextCodeUnit() <= pos.buf,
+               "should be moving forward here");
+    seekTo(pos);
+  }
+
+  MOZ_MUST_USE bool fastForward(const Position& pos,
+                                const TokenStreamAnyChars& other) {
+    MOZ_ASSERT(this->sourceUnits.addressOfNextCodeUnit() <= pos.buf,
+               "should be moving forward here");
+    return seekTo(pos, other);
+  }
 
   const Unit* codeUnitPtrAt(size_t offset) const {
     return this->sourceUnits.codeUnitPtrAt(offset);
   }
 
   const Unit* rawLimit() const { return this->sourceUnits.limit(); }
 
   MOZ_MUST_USE bool identifierName(TokenStart start, const Unit* identStart,