Bug 1159973 - Abort parsing when TokenStream::SourceCoords hits OOM. r=jorendorff, a=sylvestre
authorTooru Fujisawa <arai_a@mac.com>
Mon, 25 May 2015 17:43:36 +0900
changeset 266097 9ed526f22941
parent 266092 a080d9c9e1a9
child 266098 7de1f8e10e19
push id4753
push userarai_a@mac.com
push date2015-05-25 08:45 +0000
treeherdermozilla-beta@9ed526f22941 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjorendorff, sylvestre
bugs1159973
milestone39.0
Bug 1159973 - Abort parsing when TokenStream::SourceCoords hits OOM. r=jorendorff, a=sylvestre
js/src/asmjs/AsmJSModule.cpp
js/src/frontend/BytecodeEmitter.cpp
js/src/frontend/Parser.cpp
js/src/frontend/TokenStream.cpp
js/src/frontend/TokenStream.h
--- a/js/src/asmjs/AsmJSModule.cpp
+++ b/js/src/asmjs/AsmJSModule.cpp
@@ -2233,17 +2233,18 @@ js::LookupAsmJSModuleInCache(ExclusiveCo
     if (!cursor)
         return false;
 
     bool atEnd = cursor == entry.memory + entry.serializedSize;
     MOZ_ASSERT(atEnd, "Corrupt cache file");
     if (!atEnd)
         return true;
 
-    parser.tokenStream.advance(module->srcEndBeforeCurly());
+    if (!parser.tokenStream.advance(module->srcEndBeforeCurly()))
+        return false;
 
     {
         // No need to flush the instruction cache now, it will be flushed when
         // dynamically linking. We already know the exact extent of areas that need
         // to be patched, just make sure we flush all of them at once.
         AutoFlushICache afc("LookupAsmJSModuleInCache", /* inhibit= */ true);
         module->setAutoFlushICacheRange();
 
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -429,17 +429,20 @@ LengthOfSetLine(unsigned line)
     return 1 /* SN_SETLINE */ + (line > SN_4BYTE_OFFSET_MASK ? 4 : 1);
 }
 
 /* Updates line number notes, not column notes. */
 bool
 BytecodeEmitter::updateLineNumberNotes(uint32_t offset)
 {
     TokenStream* ts = &parser->tokenStream;
-    if (!ts->srcCoords.isOnThisLine(offset, currentLine())) {
+    bool onThisLine;
+    if (!ts->srcCoords.isOnThisLine(offset, currentLine(), &onThisLine))
+        return ts->reportError(JSMSG_OUT_OF_MEMORY);
+    if (!onThisLine) {
         unsigned line = ts->srcCoords.lineNum(offset);
         unsigned delta = line - currentLine();
 
         /*
          * Encode any change in the current source line number by using
          * either several SRC_NEWLINE notes or just one SRC_SETLINE note,
          * whichever consumes less space.
          *
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -1931,17 +1931,18 @@ Parser<FullParseHandler>::checkFunctionD
         if (!addFreeVariablesFromLazyFunction(fun, pc))
             return false;
 
         // The position passed to tokenStream.advance() is an offset of the sort
         // returned by userbuf.offset() and expected by userbuf.rawCharPtrAt(),
         // while LazyScript::{begin,end} offsets are relative to the outermost
         // script source.
         uint32_t userbufBase = lazyOuter->begin() - lazyOuter->column();
-        tokenStream.advance(fun->lazyScript()->end() - userbufBase);
+        if (!tokenStream.advance(fun->lazyScript()->end() - userbufBase))
+            return false;
 
         *pbodyProcessed = true;
         return true;
     }
 
     return true;
 }
 
--- a/js/src/frontend/TokenStream.cpp
+++ b/js/src/frontend/TokenStream.cpp
@@ -157,40 +157,40 @@ TokenStream::SourceCoords::SourceCoords(
     // appends cannot fail because |lineStartOffsets_| has statically-allocated
     // elements.
     MOZ_ASSERT(lineStartOffsets_.capacity() >= 2);
     MOZ_ALWAYS_TRUE(lineStartOffsets_.reserve(2));
     lineStartOffsets_.infallibleAppend(0);
     lineStartOffsets_.infallibleAppend(maxPtr);
 }
 
-MOZ_ALWAYS_INLINE void
+MOZ_ALWAYS_INLINE bool
 TokenStream::SourceCoords::add(uint32_t lineNum, uint32_t lineStartOffset)
 {
     uint32_t lineIndex = lineNumToIndex(lineNum);
     uint32_t sentinelIndex = lineStartOffsets_.length() - 1;
 
     MOZ_ASSERT(lineStartOffsets_[0] == 0 && lineStartOffsets_[sentinelIndex] == MAX_PTR);
 
     if (lineIndex == sentinelIndex) {
-        // We haven't seen this newline before.  Update lineStartOffsets_.
-        // We ignore any failures due to OOM -- because we always have a
-        // sentinel node, it'll just be like the newline wasn't present.  I.e.
-        // the line numbers will be wrong, but the code won't crash or anything
-        // like that.
+        // We haven't seen this newline before.  Update lineStartOffsets_
+        // only if lineStartOffsets_.append succeeds, to keep sentinel.
+        // Otherwise return false to tell TokenStream about OOM.
+        uint32_t maxPtr = MAX_PTR;
+        if (!lineStartOffsets_.append(maxPtr))
+            return false;
+
         lineStartOffsets_[lineIndex] = lineStartOffset;
-
-        uint32_t maxPtr = MAX_PTR;
-        (void)lineStartOffsets_.append(maxPtr);
-
     } else {
         // We have seen this newline before (and ungot it).  Do nothing (other
         // than checking it hasn't mysteriously changed).
-        MOZ_ASSERT(lineStartOffsets_[lineIndex] == lineStartOffset);
+        // This path can be executed after hitting OOM, so check lineIndex.
+        MOZ_ASSERT_IF(lineIndex < sentinelIndex, lineStartOffsets_[lineIndex] == lineStartOffset);
     }
+    return true;
 }
 
 MOZ_ALWAYS_INLINE bool
 TokenStream::SourceCoords::fill(const TokenStream::SourceCoords& other)
 {
     MOZ_ASSERT(lineStartOffsets_.back() == MAX_PTR);
     MOZ_ASSERT(other.lineStartOffsets_.back() == MAX_PTR);
 
@@ -355,17 +355,18 @@ TokenStream::~TokenStream()
 #endif
 
 MOZ_ALWAYS_INLINE void
 TokenStream::updateLineInfoForEOL()
 {
     prevLinebase = linebase;
     linebase = userbuf.offset();
     lineno++;
-    srcCoords.add(lineno, linebase);
+    if (!srcCoords.add(lineno, linebase))
+        flags.hitOOM = true;
 }
 
 MOZ_ALWAYS_INLINE void
 TokenStream::updateFlagsForEOL()
 {
     flags.isDirtyLine = false;
 }
 
@@ -488,27 +489,32 @@ TokenStream::TokenBuf::findEOLMax(size_t
             break;
         n++;
         if (TokenBuf::isRawEOLChar(*p++))
             break;
     }
     return start + n;
 }
 
-void
+bool
 TokenStream::advance(size_t position)
 {
     const char16_t* end = userbuf.rawCharPtrAt(position);
     while (userbuf.addressOfNextRawChar() < end)
         getChar();
 
     Token* cur = &tokens[cursor];
     cur->pos.begin = userbuf.offset();
     MOZ_MAKE_MEM_UNDEFINED(&cur->type, sizeof(cur->type));
     lookahead = 0;
+
+    if (flags.hitOOM)
+        return reportError(JSMSG_OUT_OF_MEMORY);
+
+    return true;
 }
 
 void
 TokenStream::tell(Position* pos)
 {
     pos->buf = userbuf.addressOfNextRawChar(/* allowPoisoned = */ true);
     pos->flags = flags;
     pos->lineno = lineno;
@@ -1624,23 +1630,29 @@ TokenStream::getTokenInternal(TokenKind*
       default:
         reportError(JSMSG_ILLEGAL_CHARACTER);
         goto error;
     }
 
     MOZ_CRASH("should have jumped to |out| or |error|");
 
   out:
+    if (flags.hitOOM)
+        return reportError(JSMSG_OUT_OF_MEMORY);
+
     flags.isDirtyLine = true;
     tp->pos.end = userbuf.offset();
     MOZ_ASSERT(IsTokenSane(tp));
     *ttp = tp->type;
     return true;
 
   error:
+    if (flags.hitOOM)
+        return reportError(JSMSG_OUT_OF_MEMORY);
+
     flags.isDirtyLine = true;
     tp->pos.end = userbuf.offset();
     MOZ_MAKE_MEM_UNDEFINED(&tp->type, sizeof(tp->type));
     flags.hadError = true;
 #ifdef DEBUG
     // Poisoning userbuf on error establishes an invariant: once an erroneous
     // token has been seen, userbuf will not be consulted again.  This is true
     // because the parser will deal with the illegal token by aborting parsing
--- a/js/src/frontend/TokenStream.h
+++ b/js/src/frontend/TokenStream.h
@@ -346,19 +346,20 @@ class MOZ_STACK_CLASS TokenStream
 
     struct Flags
     {
         bool isEOF:1;           // Hit end of file.
         bool isDirtyLine:1;     // Non-whitespace since start of line.
         bool sawOctalEscape:1;  // Saw an octal character escape.
         bool hadError:1;        // Hit a syntax error, at start or during a
                                 // token.
+        bool hitOOM:1;          // Hit OOM.
 
         Flags()
-          : isEOF(), isDirtyLine(), sawOctalEscape(), hadError()
+          : isEOF(), isDirtyLine(), sawOctalEscape(), hadError(), hitOOM()
         {}
     };
 
   public:
     // Sometimes the parser needs to modify how tokens are created.
     enum Modifier
     {
         None,           // Normal operation.
@@ -430,20 +431,25 @@ class MOZ_STACK_CLASS TokenStream
     peekTokenSameLine(TokenKind* ttp, Modifier modifier = None) {
         const Token& curr = currentToken();
 
         // If lookahead != 0, we have scanned ahead at least one token, and
         // |lineno| is the line that the furthest-scanned token ends on.  If
         // it's the same as the line that the current token ends on, that's a
         // stronger condition than what we are looking for, and we don't need
         // to return TOK_EOL.
-        if (lookahead != 0 && srcCoords.isOnThisLine(curr.pos.end, lineno)) {
-            MOZ_ASSERT(!flags.hadError);
-            *ttp = tokens[(cursor + 1) & ntokensMask].type;
-            return true;
+        if (lookahead != 0) {
+            bool onThisLine;
+            if (!srcCoords.isOnThisLine(curr.pos.end, lineno, &onThisLine))
+                return reportError(JSMSG_OUT_OF_MEMORY);
+            if (onThisLine) {
+                MOZ_ASSERT(!flags.hadError);
+                *ttp = tokens[(cursor + 1) & ntokensMask].type;
+                return true;
+            }
         }
 
         // The above check misses two cases where we don't have to return
         // TOK_EOL.
         // - The next token starts on the same line, but is a multi-line token.
         // - The next token starts on the same line, but lookahead==2 and there
         //   is a newline between the next token and the one after that.
         // The following test is somewhat expensive but gets these cases (and
@@ -520,17 +526,17 @@ class MOZ_STACK_CLASS TokenStream
         unsigned lineno;
         size_t linebase;
         size_t prevLinebase;
         Token currentToken;
         unsigned lookahead;
         Token lookaheadTokens[maxLookahead];
     };
 
-    void advance(size_t position);
+    bool advance(size_t position);
     void tell(Position*);
     void seek(const Position& pos);
     bool seek(const Position& pos, const TokenStream& other);
 #ifdef DEBUG
     inline bool debugHasNoLookahead() const {
         return lookahead == 0;
     }
 #endif
@@ -621,24 +627,26 @@ class MOZ_STACK_CLASS TokenStream
         static const uint32_t MAX_PTR = UINT32_MAX;
 
         uint32_t lineIndexToNum(uint32_t lineIndex) const { return lineIndex + initialLineNum_; }
         uint32_t lineNumToIndex(uint32_t lineNum)   const { return lineNum   - initialLineNum_; }
 
       public:
         SourceCoords(ExclusiveContext* cx, uint32_t ln);
 
-        void add(uint32_t lineNum, uint32_t lineStartOffset);
+        bool add(uint32_t lineNum, uint32_t lineStartOffset);
         bool fill(const SourceCoords& other);
 
-        bool isOnThisLine(uint32_t offset, uint32_t lineNum) const {
+        bool isOnThisLine(uint32_t offset, uint32_t lineNum, bool* onThisLine) const {
             uint32_t lineIndex = lineNumToIndex(lineNum);
-            MOZ_ASSERT(lineIndex + 1 < lineStartOffsets_.length());  // +1 due to sentinel
-            return lineStartOffsets_[lineIndex] <= offset &&
-                   offset < lineStartOffsets_[lineIndex + 1];
+            if (lineIndex + 1 >= lineStartOffsets_.length()) // +1 due to sentinel
+                return false;
+            *onThisLine = lineStartOffsets_[lineIndex] <= offset &&
+                          offset < lineStartOffsets_[lineIndex + 1];
+            return true;
         }
 
         uint32_t lineNum(uint32_t offset) const;
         uint32_t columnIndex(uint32_t offset) const;
         void lineNumAndColumnIndex(uint32_t offset, uint32_t* lineNum, uint32_t* columnIndex) const;
     };
 
     SourceCoords srcCoords;