Bug 1347711 - Refactor error reporting out of TokenStream;r?shu draft
authorDavid Teller <dteller@mozilla.com>
Mon, 20 Mar 2017 16:22:19 +0100
changeset 502267 91557797eee41bd91b2b4b95e109949a455ab345
parent 496677 68e6acf47dabf0c1aff5e3b8ab246a4bce63d645
child 550113 54cd8e913469d9698cfd1646830ac3626638ef0e
push id50232
push userdteller@mozilla.com
push dateTue, 21 Mar 2017 14:44:52 +0000
reviewersshu
bugs1347711
milestone55.0a1
Bug 1347711 - Refactor error reporting out of TokenStream;r?shu Prior to a large refactoring/templatization on TokenStream, let's get error reporting out of the way. MozReview-Commit-ID: 9fMWfOhRHSL
js/src/frontend/BytecodeEmitter.cpp
js/src/frontend/ErrorReport.cpp
js/src/frontend/ErrorReport.h
js/src/frontend/Parser.cpp
js/src/frontend/Parser.h
js/src/frontend/SourceCoords.h
js/src/frontend/TokenStream.cpp
js/src/frontend/TokenStream.h
js/src/irregexp/RegExpParser.cpp
js/src/moz.build
js/src/wasm/AsmJS.cpp
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -10,16 +10,17 @@
 
 #include "frontend/BytecodeEmitter.h"
 
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/DebugOnly.h"
 #include "mozilla/FloatingPoint.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/PodOperations.h"
+#include "mozilla/Unused.h"
 
 #include <string.h>
 
 #include "jsapi.h"
 #include "jsatom.h"
 #include "jscntxt.h"
 #include "jsfun.h"
 #include "jsnum.h"
@@ -2488,17 +2489,17 @@ LengthOfSetLine(unsigned line)
 
 /* Updates line number notes, not column notes. */
 bool
 BytecodeEmitter::updateLineNumberNotes(uint32_t offset)
 {
     TokenStream* ts = &parser->tokenStream;
     bool onThisLine;
     if (!ts->srcCoords.isOnThisLine(offset, currentLine(), &onThisLine))
-        return ts->reportError(JSMSG_OUT_OF_MEMORY);
+        return ReportTokenError(ts, 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.
@@ -3561,48 +3562,54 @@ inline TokenStream&
 BytecodeEmitter::tokenStream()
 {
     return parser->tokenStream;
 }
 
 bool
 BytecodeEmitter::reportError(ParseNode* pn, unsigned errorNumber, ...)
 {
-    TokenPos pos = pn ? pn->pn_pos : tokenStream().currentToken().pos;
-
     va_list args;
     va_start(args, errorNumber);
-    bool result = tokenStream().reportCompileErrorNumberVA(nullptr, pos.begin, JSREPORT_ERROR,
-                                                           errorNumber, args);
+
+    OffsetErrorContext context = pn
+        ? tokenStream().atOffset(pn->pn_pos.begin)
+        : tokenStream().asOffsetErrorContext();
+    bool result = ReportCompileErrorNumberVA(&context, nullptr, JSREPORT_ERROR,
+                                             errorNumber, args);
     va_end(args);
     return result;
 }
 
 bool
 BytecodeEmitter::reportExtraWarning(ParseNode* pn, unsigned errorNumber, ...)
 {
-    TokenPos pos = pn ? pn->pn_pos : tokenStream().currentToken().pos;
-
     va_list args;
     va_start(args, errorNumber);
-    bool result = tokenStream().reportExtraWarningErrorNumberVA(nullptr, pos.begin,
-                                                                errorNumber, args);
+
+    OffsetErrorContext context = pn
+        ? tokenStream().atOffset(pn->pn_pos.begin)
+        : tokenStream().asOffsetErrorContext();
+    bool result = ReportExtraWarningErrorNumberVA(&context, nullptr,
+                                                  errorNumber, args);
     va_end(args);
     return result;
 }
 
 bool
 BytecodeEmitter::reportStrictModeError(ParseNode* pn, unsigned errorNumber, ...)
 {
-    TokenPos pos = pn ? pn->pn_pos : tokenStream().currentToken().pos;
-
     va_list args;
     va_start(args, errorNumber);
-    bool result = tokenStream().reportStrictModeErrorNumberVA(nullptr, pos.begin, sc->strict(),
-                                                              errorNumber, args);
+
+    OffsetErrorContext context = pn
+        ? tokenStream().atOffset(pn->pn_pos.begin)
+        : tokenStream().asOffsetErrorContext();
+    bool result = ReportStrictModeErrorNumberVA(&context, nullptr, sc->strict(),
+                                                errorNumber, args);
     va_end(args);
     return result;
 }
 
 bool
 BytecodeEmitter::emitNewInit(JSProtoKey key)
 {
     const size_t len = 1 + UINT32_INDEX_LEN;
@@ -4459,17 +4466,17 @@ BytecodeEmitter::emitSwitch(ParseNode* p
     // After entering the scope, push the switch control.
     BreakableControl controlInfo(this, StatementKind::Switch);
 
     ptrdiff_t top = offset();
 
     // Switch bytecodes run from here till end of final case.
     uint32_t caseCount = cases->pn_count;
     if (caseCount > JS_BIT(16)) {
-        parser->tokenStream.reportError(JSMSG_TOO_MANY_CASES);
+        mozilla::Unused << ReportTokenError(&parser->tokenStream, JSMSG_TOO_MANY_CASES);
         return false;
     }
 
     // Try for most optimal, fall back if not dense ints.
     JSOp switchOp = JSOP_TABLESWITCH;
     uint32_t tableLength = 0;
     int32_t low, high;
     bool hasDefault = false;
@@ -9081,19 +9088,19 @@ BytecodeEmitter::emitCallOrNew(ParseNode
      * Then (or in a call case that has no explicit reference-base
      * object) we emit JSOP_UNDEFINED to produce the undefined |this|
      * value required for calls (which non-strict mode functions
      * will box into the global object).
      */
     uint32_t argc = pn->pn_count - 1;
 
     if (argc >= ARGC_LIMIT) {
-        parser->tokenStream.reportError(callop
-                                        ? JSMSG_TOO_MANY_FUN_ARGS
-                                        : JSMSG_TOO_MANY_CON_ARGS);
+        mozilla::Unused << ReportTokenError(&parser->tokenStream, callop
+                                                                ? JSMSG_TOO_MANY_FUN_ARGS
+                                                                : JSMSG_TOO_MANY_CON_ARGS);
         return false;
     }
 
     ParseNode* pn2 = pn->pn_head;
     bool spread = JOF_OPTYPE(pn->getOp()) == JOF_BYTE;
     switch (pn2->getKind()) {
       case PNK_NAME:
         if (emitterMode == BytecodeEmitter::SelfHosting && !spread) {
@@ -10855,17 +10862,17 @@ BytecodeEmitter::addToSrcNoteDelta(jssrc
     }
     return true;
 }
 
 bool
 BytecodeEmitter::setSrcNoteOffset(unsigned index, unsigned which, ptrdiff_t offset)
 {
     if (!SN_REPRESENTABLE_OFFSET(offset)) {
-        parser->tokenStream.reportError(JSMSG_NEED_DIET, js_script_str);
+        mozilla::Unused << ReportTokenError(&parser->tokenStream, JSMSG_NEED_DIET, js_script_str);
         return false;
     }
 
     SrcNotesVector& notes = this->notes();
 
     /* Find the offset numbered which (i.e., skip exactly which offsets). */
     jssrcnote* sn = &notes[index];
     MOZ_ASSERT(SN_TYPE(sn) != SRC_XDELTA);
new file mode 100644
--- /dev/null
+++ b/js/src/frontend/ErrorReport.cpp
@@ -0,0 +1,266 @@
+#include "frontend/ErrorReport.h"
+#include "vm/StringBuffer.h"
+
+namespace js {
+namespace frontend {
+
+namespace {
+
+// By convention, if the offset is `NoOffset`, the error doesn't have an offset.
+const uint32_t NoOffset = UINT32_MAX;
+
+}
+
+//----- Implementation of ErrorContext and OffsetErrorContext
+
+OffsetErrorContext::OffsetErrorContext(ErrorContext* original, uint32_t offset):
+    wrapped_(original),
+    offset_(offset)
+{ }
+
+uint32_t
+OffsetErrorContext::currentPosition() const {
+    return offset_;
+}
+
+const ReadOnlyCompileOptions&
+OffsetErrorContext::options() const {
+    return wrapped_->options();
+}
+
+JSContext*
+OffsetErrorContext::context() const {
+    return wrapped_->context();
+}
+
+bool
+OffsetErrorContext::strictMode() const {
+    return wrapped_->strictMode();
+}
+
+const char*
+OffsetErrorContext::getFilename() const {
+    return wrapped_->getFilename();
+}
+
+const class SourceCoords&
+OffsetErrorContext::sourceCoords() const {
+    return wrapped_->sourceCoords();
+}
+
+unsigned
+OffsetErrorContext::lineNumber() const {
+    return wrapped_->lineNumber();
+}
+
+bool
+OffsetErrorContext::fillComplaint(JSErrorReport& err) {
+    return wrapped_->fillComplaint(err);
+}
+
+bool
+OffsetErrorContext::hasMutedErrors() const {
+    return wrapped_->hasMutedErrors();
+}
+
+OffsetErrorContext ErrorContext::asOffsetErrorContext() {
+    return OffsetErrorContext(this, currentPosition());
+}
+
+OffsetErrorContext ErrorContext::noOffset() {
+    return OffsetErrorContext(this, UINT32_MAX);
+}
+
+OffsetErrorContext ErrorContext::atOffset(uint32_t offset) {
+    return OffsetErrorContext(this, offset);
+}
+
+// ----- Actual error reporting
+
+void
+ReportInvalidEscapeError(ErrorContext* cx, InvalidEscapeType type) {
+    switch (type) {
+        case InvalidEscapeType::None:
+            MOZ_ASSERT_UNREACHABLE("unexpected InvalidEscapeType");
+            return;
+        case InvalidEscapeType::Hexadecimal:
+            IgnoreReportTokenError(cx, JSMSG_MALFORMED_ESCAPE, "hexadecimal");
+            return;
+        case InvalidEscapeType::Unicode:
+            IgnoreReportTokenError(cx, JSMSG_MALFORMED_ESCAPE, "Unicode");
+            return;
+        case InvalidEscapeType::UnicodeOverflow:
+            IgnoreReportTokenError(cx, JSMSG_UNICODE_OVERFLOW, "escape sequence");
+            return;
+        case InvalidEscapeType::Octal:
+            IgnoreReportTokenError(cx, JSMSG_DEPRECATED_OCTAL);
+            return;
+    }
+}
+
+bool
+ReportCompileErrorNumberVA(ErrorContext* context,
+                           UniquePtr<JSErrorNotes> notes,
+                           unsigned flags, unsigned errorNumber, va_list args)
+{
+    bool warning = JSREPORT_IS_WARNING(flags);
+    const uint32_t offset = context->currentPosition();
+
+    if (warning && context->options().werrorOption) {
+        flags &= ~JSREPORT_WARNING;
+        warning = false;
+    }
+
+    // On the active thread, report the error immediately. When compiling off
+    // thread, save the error so that the thread finishing the parse can report
+    // it later.
+    CompileError tempErr;
+    CompileError* tempErrPtr = &tempErr;
+    JSContext* cx = context->context();
+    if (cx->helperThread() && !cx->addPendingCompileError(&tempErrPtr))
+        return false;
+    CompileError& err = *tempErrPtr;
+
+    err.notes = Move(notes);
+    err.flags = flags;
+    err.errorNumber = errorNumber;
+    err.filename = context->getFilename();
+    err.isMuted = context->hasMutedErrors();
+    const SourceCoords& srcCoords = context->sourceCoords();
+    if (offset == NoOffset) {
+        err.lineno = 0;
+        err.column = 0;
+    } else {
+        err.lineno = srcCoords.lineNum(offset);
+        err.column = srcCoords.columnIndex(offset);
+    }
+
+    // If we have no location information, try to get one from the caller.
+    bool callerFilename = false;
+    if (offset != NoOffset && !err.filename && !cx->helperThread()) {
+        NonBuiltinFrameIter iter(cx,
+                                 FrameIter::FOLLOW_DEBUGGER_EVAL_PREV_LINK,
+                                 cx->compartment()->principals());
+        if (!iter.done() && iter.filename()) {
+            callerFilename = true;
+            err.filename = iter.filename();
+            err.lineno = iter.computeLine(&err.column);
+        }
+    }
+
+    if (!ExpandErrorArgumentsVA(cx, GetErrorMessage, nullptr, errorNumber,
+                                nullptr, ArgumentsAreLatin1, &err, args))
+    {
+        return false;
+    }
+
+    // Given a token, T, that we want to complain about: if T's (starting)
+    // lineno doesn't match TokenStream's lineno, that means we've scanned past
+    // the line that T starts on, which makes it hard to print some or all of
+    // T's (starting) line for context.
+    //
+    // So we don't even try, leaving report.linebuf and friends zeroed.  This
+    // means that any error involving a multi-line token (e.g. an unterminated
+    // multi-line string literal) won't have a context printed.
+    if (offset != NoOffset && err.lineno == context->lineNumber() && !callerFilename) {
+        if (!context->fillComplaint(err)) {
+            return false;
+        }
+    }
+
+    if (!cx->helperThread())
+        err.throwError(cx);
+
+    return warning;
+}
+
+bool
+ReportStrictModeTokenError(ErrorContext* context, unsigned errorNumber, ...)
+{
+    va_list args;
+    va_start(args, errorNumber);
+    bool result = ReportStrictModeErrorNumberVA(context, nullptr,
+                                                context->strictMode(), errorNumber, args);
+    va_end(args);
+    return result;
+}
+
+bool
+ReportTokenError(ErrorContext* context, unsigned errorNumber, ...)
+{
+    va_list args;
+    va_start(args, errorNumber);
+    bool result = ReportCompileErrorNumberVA(context, nullptr,
+                                             JSREPORT_ERROR, errorNumber, args);
+    va_end(args);
+    return result;
+}
+
+void
+IgnoreReportTokenError(ErrorContext* context, unsigned errorNumber, ...)
+{
+    va_list args;
+    va_start(args, errorNumber);
+#ifdef DEBUG
+    bool result =
+#endif
+        ReportCompileErrorNumberVA(context, nullptr, JSREPORT_ERROR,
+                                   errorNumber, args);
+    MOZ_ASSERT(!result, "reporting an error returned true?");
+    va_end(args);
+}
+
+bool
+ReportWarning(ErrorContext* context, unsigned errorNumber, ...)
+{
+    va_list args;
+    va_start(args, errorNumber);
+    bool result = ReportCompileErrorNumberVA(context, nullptr,
+                                             JSREPORT_WARNING, errorNumber, args);
+    va_end(args);
+    return result;
+}
+
+bool
+ReportExtraWarningErrorNumberVA(ErrorContext* context,
+    UniquePtr<JSErrorNotes> notes, unsigned errorNumber, va_list args)
+{
+    if (!context->options().extraWarningsOption)
+        return true;
+
+    return ReportCompileErrorNumberVA(context, Move(notes),
+        JSREPORT_STRICT|JSREPORT_WARNING, errorNumber, args);
+}
+
+void
+ReportAsmJSError(ErrorContext* context, unsigned errorNumber, ...)
+{
+    va_list args;
+    va_start(args, errorNumber);
+    unsigned flags = context->options().throwOnAsmJSValidationFailureOption
+                     ? JSREPORT_ERROR
+                     : JSREPORT_WARNING;
+    ReportCompileErrorNumberVA(context, nullptr, flags, errorNumber, args);
+    va_end(args);
+}
+
+bool
+ReportStrictModeErrorNumberVA(ErrorContext* context,
+    UniquePtr<JSErrorNotes> notes,
+    bool strictMode, unsigned errorNumber, va_list args)
+{
+    // In strict mode code, this is an error, not merely a warning.
+    unsigned flags;
+    if (strictMode)
+        flags = JSREPORT_ERROR;
+    else if (context->options().extraWarningsOption)
+        flags = JSREPORT_WARNING | JSREPORT_STRICT;
+    else
+        return true;
+
+    return ReportCompileErrorNumberVA(context, Move(notes), flags, errorNumber, args);
+}
+
+} // namespace frontend
+} // namespace js
+
new file mode 100644
--- /dev/null
+++ b/js/src/frontend/ErrorReport.h
@@ -0,0 +1,139 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef frontend_ErrorReport_h
+#define frontend_ErrorReport_h
+
+// Error reporting for the tokenizer
+
+#include "jsapi.h"
+#include "NamespaceImports.h"
+#include "frontend/SourceCoords.h"
+
+namespace js {
+namespace frontend {
+
+class OffsetErrorContext;
+
+// Additional data on an error.
+//
+// This (almost) purely virtual class provides signatures to access the
+// context data maintained by TokenStream, without having to depend on
+// the full implementation of TokenStream.
+class ErrorContext
+{
+  public:
+    virtual JSContext* context() const = 0;
+
+    // The current position in characters, or UINT32_MAX if
+    // there is no position.
+    virtual uint32_t currentPosition() const = 0;
+
+    // The number of the current line.
+    virtual unsigned lineNumber() const = 0;
+
+    // The options with which the source is compiled.
+    virtual const JS::ReadOnlyCompileOptions& options() const = 0;
+
+    // `true` if we are operating in strict mode, `false` otherwise.
+    virtual bool strictMode() const = 0;
+
+    // The name of the file being parsed.
+    virtual const char* getFilename() const = 0;
+
+    // `true` if errors should be sanitized ("muted"), as specified
+    // by HTML5 security.
+    virtual bool hasMutedErrors() const = 0;
+
+    // Access to the source line-to-offset mappings.
+    virtual const class SourceCoords& sourceCoords() const = 0;
+
+    // Add an error message displaying the context of a syntax error.
+    virtual bool fillComplaint(JSErrorReport& err) = 0;
+
+    // Return an OffsetErrorContext based on `this`, but without an offset.
+    OffsetErrorContext noOffset();
+
+    // Return an OffsetErrorContext based on `this`, but with a different offset.
+    OffsetErrorContext atOffset(uint32_t offset);
+
+    // Return an OffsetErrorContext with the same behavior as `this`.
+    OffsetErrorContext asOffsetErrorContext();
+};
+
+// Utility class providing the ability to use an `ErrorContext` but to specify
+// an error that happened at a different offset or without any offset.
+class OffsetErrorContext: public ErrorContext MOZ_STACK_CLASS {
+public:
+    OffsetErrorContext(ErrorContext*, uint32_t offset);
+
+public:
+    virtual uint32_t currentPosition() const override final;
+    virtual const ReadOnlyCompileOptions& options() const override final;
+    virtual JSContext* context() const override final;
+    virtual bool strictMode() const override final;
+    virtual const char* getFilename() const override final;
+    virtual bool hasMutedErrors() const override final;
+    virtual const class SourceCoords& sourceCoords() const override final;
+    virtual unsigned lineNumber() const override final;
+    virtual bool fillComplaint(JSErrorReport& err) override final;
+
+private:
+    // The ErrorContext providing all the data.
+    ErrorContext* wrapped_;
+
+    // An offset for the error. By convention, UINT32_MAX for "no offset".
+    const uint32_t offset_;
+};
+
+enum class InvalidEscapeType {
+    // No invalid character escapes.
+    None,
+    // A malformed \x escape.
+    Hexadecimal,
+    // A malformed \u escape.
+    Unicode,
+    // An otherwise well-formed \u escape which represents a
+    // codepoint > 10FFFF.
+    UnicodeOverflow,
+    // An octal escape in a template token.
+    Octal
+};
+
+//---- Specialized error reporters
+
+void ReportInvalidEscapeError(ErrorContext* context, InvalidEscapeType type);
+
+MOZ_MUST_USE bool ReportTokenError(ErrorContext* context, unsigned errorNumber, ...);
+// As `reportTokenError`, but asserts that the error was reported instead of returning a `bool`.
+void IgnoreReportTokenError(ErrorContext* context, unsigned errorNumber, ...);
+
+MOZ_MUST_USE bool ReportStrictModeTokenError(ErrorContext* context, unsigned errorNumber, ...);
+
+MOZ_MUST_USE bool ReportWarning(ErrorContext* context, unsigned errorNumber, ...);
+
+// asm.js reporter
+void ReportAsmJSError(ErrorContext* context, unsigned errorNumber, ...);
+
+
+
+//--- General-purpose error reporters
+
+// General-purpose error reporters.  You should avoid calling these
+// directly, and instead use the more succinct alternatives (reportTokenError(),
+// reportWarning(), &c.) in TokenStream, Parser, and BytecodeEmitter.
+bool ReportCompileErrorNumberVA(ErrorContext* context, UniquePtr<JSErrorNotes> notes, unsigned flags,
+                                unsigned errorNumber, va_list args);
+bool ReportStrictModeErrorNumberVA(ErrorContext* context, UniquePtr<JSErrorNotes> notes,
+                                   bool strictMode, unsigned errorNumber, va_list args);
+bool ReportExtraWarningErrorNumberVA(ErrorContext* context, UniquePtr<JSErrorNotes> notes,
+                                     unsigned errorNumber, va_list args);
+
+
+}
+}
+
+#endif // frontend_ErrorReport_h
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -324,17 +324,17 @@ EvalSharedContext::EvalSharedContext(JSC
         }
     }
 }
 
 bool
 ParseContext::init()
 {
     if (scriptId_ == UINT32_MAX) {
-        tokenStream_.reportError(JSMSG_NEED_DIET, js_script_str);
+        mozilla::Unused << ReportTokenError(&tokenStream_, JSMSG_NEED_DIET, js_script_str);
         return false;
     }
 
     JSContext* cx = sc()->context;
 
     if (isFunctionBox()) {
         // Named lambdas always need a binding for their own name. If this
         // binding is closed over when we finish parsing the function in
@@ -588,150 +588,165 @@ FunctionBox::initWithEnclosingScope(Scop
     computeInWith(enclosingScope);
 }
 
 void
 ParserBase::error(unsigned errorNumber, ...)
 {
     va_list args;
     va_start(args, errorNumber);
+    OffsetErrorContext context = tokenStream.atOffset(pos().begin);
 #ifdef DEBUG
     bool result =
 #endif
-        tokenStream.reportCompileErrorNumberVA(nullptr, pos().begin, JSREPORT_ERROR,
-                                               errorNumber, args);
+        ReportCompileErrorNumberVA(&context, nullptr, JSREPORT_ERROR,
+                                   errorNumber, args);
     MOZ_ASSERT(!result, "reporting an error returned true?");
     va_end(args);
 }
 
 void
 ParserBase::errorWithNotes(UniquePtr<JSErrorNotes> notes, unsigned errorNumber, ...)
 {
     va_list args;
     va_start(args, errorNumber);
+    OffsetErrorContext context = tokenStream.atOffset(pos().begin);
 #ifdef DEBUG
     bool result =
 #endif
-        tokenStream.reportCompileErrorNumberVA(Move(notes), pos().begin, JSREPORT_ERROR,
-                                               errorNumber, args);
+        ReportCompileErrorNumberVA(&context, Move(notes), JSREPORT_ERROR,
+                                   errorNumber, args);
     MOZ_ASSERT(!result, "reporting an error returned true?");
     va_end(args);
 }
 
 void
 ParserBase::errorAt(uint32_t offset, unsigned errorNumber, ...)
 {
     va_list args;
     va_start(args, errorNumber);
+    OffsetErrorContext context = tokenStream.atOffset(offset);
 #ifdef DEBUG
     bool result =
 #endif
-        tokenStream.reportCompileErrorNumberVA(nullptr, offset, JSREPORT_ERROR, errorNumber, args);
+        ReportCompileErrorNumberVA(&context, nullptr, JSREPORT_ERROR, errorNumber, args);
     MOZ_ASSERT(!result, "reporting an error returned true?");
     va_end(args);
 }
 
 void
 ParserBase::errorWithNotesAt(UniquePtr<JSErrorNotes> notes, uint32_t offset,
                              unsigned errorNumber, ...)
 {
     va_list args;
     va_start(args, errorNumber);
+
+    OffsetErrorContext context = tokenStream.atOffset(offset);
 #ifdef DEBUG
     bool result =
 #endif
-        tokenStream.reportCompileErrorNumberVA(Move(notes), offset, JSREPORT_ERROR,
+        ReportCompileErrorNumberVA(&context, Move(notes), JSREPORT_ERROR,
                                                errorNumber, args);
     MOZ_ASSERT(!result, "reporting an error returned true?");
     va_end(args);
 }
 
 bool
 ParserBase::warning(unsigned errorNumber, ...)
 {
     va_list args;
     va_start(args, errorNumber);
+
+    OffsetErrorContext context = tokenStream.atOffset(pos().begin);
     bool result =
-        tokenStream.reportCompileErrorNumberVA(nullptr, pos().begin, JSREPORT_WARNING,
-                                               errorNumber, args);
+        ReportCompileErrorNumberVA(&context, nullptr, JSREPORT_WARNING,
+                                   errorNumber, args);
     va_end(args);
     return result;
 }
 
 bool
 ParserBase::warningAt(uint32_t offset, unsigned errorNumber, ...)
 {
     va_list args;
     va_start(args, errorNumber);
+
+    OffsetErrorContext context = tokenStream.atOffset(offset);
     bool result =
-        tokenStream.reportCompileErrorNumberVA(nullptr, offset, JSREPORT_WARNING,
-                                               errorNumber, args);
+        ReportCompileErrorNumberVA(&context, nullptr, JSREPORT_WARNING,
+                                   errorNumber, args);
     va_end(args);
     return result;
 }
 
 bool
 ParserBase::extraWarning(unsigned errorNumber, ...)
 {
     va_list args;
     va_start(args, errorNumber);
-    bool result = tokenStream.reportExtraWarningErrorNumberVA(nullptr, pos().begin,
-                                                              errorNumber, args);
+
+    OffsetErrorContext context = tokenStream.atOffset(pos().begin);
+    bool result = ReportExtraWarningErrorNumberVA(&context, nullptr,
+                                                  errorNumber, args);
     va_end(args);
     return result;
 }
 
 bool
 ParserBase::strictModeError(unsigned errorNumber, ...)
 {
     va_list args;
     va_start(args, errorNumber);
+
+    OffsetErrorContext context = tokenStream.atOffset(pos().begin);
     bool res =
-        tokenStream.reportStrictModeErrorNumberVA(nullptr, pos().begin, pc->sc()->strict(),
-                                                  errorNumber, args);
+        ReportStrictModeErrorNumberVA(&context, nullptr, pc->sc()->strict(),
+                                      errorNumber, args);
     va_end(args);
     return res;
 }
 
 bool
 ParserBase::strictModeErrorAt(uint32_t offset, unsigned errorNumber, ...)
 {
     va_list args;
     va_start(args, errorNumber);
+
+    OffsetErrorContext context = tokenStream.atOffset(offset);
     bool res =
-        tokenStream.reportStrictModeErrorNumberVA(nullptr, offset, pc->sc()->strict(),
-                                                  errorNumber, args);
+        ReportStrictModeErrorNumberVA(&context, nullptr, pc->sc()->strict(),
+                                      errorNumber, args);
     va_end(args);
     return res;
 }
 
 bool
 ParserBase::reportNoOffset(ParseReportKind kind, bool strict, unsigned errorNumber, ...)
 {
     va_list args;
     va_start(args, errorNumber);
     bool result = false;
-    uint32_t offset = TokenStream::NoOffset;
+    OffsetErrorContext context = tokenStream.noOffset();
     switch (kind) {
       case ParseError:
-        result = tokenStream.reportCompileErrorNumberVA(nullptr, offset, JSREPORT_ERROR,
-                                                        errorNumber, args);
+        result = ReportCompileErrorNumberVA(&context, nullptr, JSREPORT_ERROR,
+                                            errorNumber, args);
         break;
       case ParseWarning:
         result =
-            tokenStream.reportCompileErrorNumberVA(nullptr, offset, JSREPORT_WARNING,
-                                                   errorNumber, args);
+            ReportCompileErrorNumberVA(&context, nullptr, JSREPORT_WARNING,
+                                       errorNumber, args);
         break;
       case ParseExtraWarning:
-        result = tokenStream.reportExtraWarningErrorNumberVA(nullptr, offset,
-                                                             errorNumber, args);
+        result = ReportExtraWarningErrorNumberVA(&context, nullptr,
+                                                 errorNumber, args);
         break;
       case ParseStrictError:
-        result = tokenStream.reportStrictModeErrorNumberVA(nullptr, offset, strict,
-                                                           errorNumber, args);
+        result = ReportStrictModeErrorNumberVA(&context, nullptr, strict,
+                                               errorNumber, args);
         break;
     }
     va_end(args);
     return result;
 }
 
 template <>
 bool
--- a/js/src/frontend/Parser.h
+++ b/js/src/frontend/Parser.h
@@ -7,16 +7,17 @@
 /* JS parser. */
 
 #ifndef frontend_Parser_h
 #define frontend_Parser_h
 
 #include "mozilla/Array.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/TypeTraits.h"
+#include "mozilla/Unused.h"
 
 #include "jspubtd.h"
 
 #include "frontend/BytecodeCompiler.h"
 #include "frontend/FullParseHandler.h"
 #include "frontend/NameAnalysisTypes.h"
 #include "frontend/NameCollections.h"
 #include "frontend/SharedContext.h"
@@ -126,17 +127,18 @@ class ParseContext : public Nestable<Par
         void dump(ParseContext* pc);
 
         uint32_t id() const {
             return id_;
         }
 
         MOZ_MUST_USE bool init(ParseContext* pc) {
             if (id_ == UINT32_MAX) {
-                pc->tokenStream_.reportError(JSMSG_NEED_DIET, js_script_str);
+                mozilla::Unused << ReportTokenError(&pc->tokenStream_, JSMSG_NEED_DIET,
+                    js_script_str);
                 return false;
             }
 
             return declared_.acquire(pc->sc()->context);
         }
 
         DeclaredNamePtr lookupDeclaredName(JSAtom* name) {
             return declared_->lookup(name);
new file mode 100644
--- /dev/null
+++ b/js/src/frontend/SourceCoords.h
@@ -0,0 +1,88 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef frontend_SourceCoords_h
+#define frontend_SourceCoords_h
+
+#include "jsapi.h"
+
+namespace js {
+namespace frontend {
+
+// This class maps a userbuf offset (which is 0-indexed) to a line number
+// (which is 1-indexed) and a column index (which is 0-indexed).
+class SourceCoords
+{
+    // For a given buffer holding source code, |lineStartOffsets_| has one
+    // element per line of source code, plus one sentinel element.  Each
+    // non-sentinel element holds the buffer offset for the start of the
+    // corresponding line of source code.  For this example script:
+    //
+    // 1  // xyz            [line starts at offset 0]
+    // 2  var x;            [line starts at offset 7]
+    // 3                    [line starts at offset 14]
+    // 4  var y;            [line starts at offset 15]
+    //
+    // |lineStartOffsets_| is:
+    //
+    //   [0, 7, 14, 15, MAX_PTR]
+    //
+    // To convert a "line number" to a "line index" (i.e. an index into
+    // |lineStartOffsets_|), subtract |initialLineNum_|.  E.g. line 3's
+    // line index is (3 - initialLineNum_), which is 2.  Therefore
+    // lineStartOffsets_[2] holds the buffer offset for the start of line 3,
+    // which is 14.  (Note that |initialLineNum_| is often 1, but not
+    // always.)
+    //
+    // The first element is always 0, and the last element is always the
+    // MAX_PTR sentinel.
+    //
+    // offset-to-line/column lookups are O(log n) in the worst case (binary
+    // search), but in practice they're heavily clustered and we do better
+    // than that by using the previous lookup's result (lastLineIndex_) as
+    // a starting point.
+    //
+    // Checking if an offset lies within a particular line number
+    // (isOnThisLine()) is O(1).
+    //
+    Vector<uint32_t, 128> lineStartOffsets_;
+    uint32_t            initialLineNum_;
+
+    // This is mutable because it's modified on every search, but that fact
+    // isn't visible outside this class.
+    mutable uint32_t    lastLineIndex_;
+
+    uint32_t lineIndexOf(uint32_t offset) const;
+
+    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(JSContext* cx, uint32_t ln);
+
+    MOZ_MUST_USE bool add(uint32_t lineNum, uint32_t lineStartOffset);
+    MOZ_MUST_USE bool fill(const SourceCoords& other);
+
+    bool isOnThisLine(uint32_t offset, uint32_t lineNum, bool* onThisLine) const {
+        uint32_t lineIndex = lineNumToIndex(lineNum);
+        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;
+};
+
+} // namespace frontend
+} // namespace js
+
+#endif // frontend_SourceCoords_h
\ No newline at end of file
--- a/js/src/frontend/TokenStream.cpp
+++ b/js/src/frontend/TokenStream.cpp
@@ -1,16 +1,17 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
  * vim: set ts=8 sts=4 et sw=4 tw=99:
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 // JS lexical scanner.
 
+#include "frontend/ErrorReport.h"
 #include "frontend/TokenStream.h"
 
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/IntegerTypeTraits.h"
 #include "mozilla/PodOperations.h"
 
 #include <ctype.h>
 #include <stdarg.h>
@@ -247,39 +248,39 @@ TokenStream::reservedWordToPropertyName(
       FOR_EACH_JAVASCRIPT_RESERVED_WORD(EMIT_CASE)
 #undef EMIT_CASE
       default:
         MOZ_ASSERT_UNREACHABLE("Not a reserved word TokenKind.");
     }
     return nullptr;
 }
 
-TokenStream::SourceCoords::SourceCoords(JSContext* cx, uint32_t ln)
+SourceCoords::SourceCoords(JSContext* cx, uint32_t ln)
   : lineStartOffsets_(cx), initialLineNum_(ln), lastLineIndex_(0)
 {
     // This is actually necessary!  Removing it causes compile errors on
     // GCC and clang.  You could try declaring this:
     //
-    //   const uint32_t TokenStream::SourceCoords::MAX_PTR;
+    //   const uint32_t SourceCoords::MAX_PTR;
     //
     // which fixes the GCC/clang error, but causes bustage on Windows.  Sigh.
     //
     uint32_t maxPtr = MAX_PTR;
 
     // The first line begins at buffer offset 0.  MAX_PTR is the sentinel.  The
     // 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 bool
-TokenStream::SourceCoords::add(uint32_t lineNum, uint32_t lineStartOffset)
+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_
@@ -300,17 +301,17 @@ TokenStream::SourceCoords::add(uint32_t 
         // than checking it hasn't mysteriously changed).
         // 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)
+SourceCoords::fill(const SourceCoords& other)
 {
     MOZ_ASSERT(lineStartOffsets_.back() == MAX_PTR);
     MOZ_ASSERT(other.lineStartOffsets_.back() == MAX_PTR);
 
     if (lineStartOffsets_.length() >= other.lineStartOffsets_.length())
         return true;
 
     uint32_t sentinelIndex = lineStartOffsets_.length() - 1;
@@ -319,17 +320,17 @@ TokenStream::SourceCoords::fill(const To
     for (size_t i = sentinelIndex + 1; i < other.lineStartOffsets_.length(); i++) {
         if (!lineStartOffsets_.append(other.lineStartOffsets_[i]))
             return false;
     }
     return true;
 }
 
 MOZ_ALWAYS_INLINE uint32_t
-TokenStream::SourceCoords::lineIndexOf(uint32_t offset) const
+SourceCoords::lineIndexOf(uint32_t offset) const
 {
     uint32_t iMin, iMax, iMid;
 
     if (lineStartOffsets_[lastLineIndex_] <= offset) {
         // If we reach here, offset is on a line the same as or higher than
         // last time.  Check first for the +0, +1, +2 cases, because they
         // typically cover 85--98% of cases.
         if (offset < lineStartOffsets_[lastLineIndex_ + 1])
@@ -370,33 +371,33 @@ TokenStream::SourceCoords::lineIndexOf(u
     }
     MOZ_ASSERT(iMax == iMin);
     MOZ_ASSERT(lineStartOffsets_[iMin] <= offset && offset < lineStartOffsets_[iMin + 1]);
     lastLineIndex_ = iMin;
     return iMin;
 }
 
 uint32_t
-TokenStream::SourceCoords::lineNum(uint32_t offset) const
+SourceCoords::lineNum(uint32_t offset) const
 {
     uint32_t lineIndex = lineIndexOf(offset);
     return lineIndexToNum(lineIndex);
 }
 
 uint32_t
-TokenStream::SourceCoords::columnIndex(uint32_t offset) const
+SourceCoords::columnIndex(uint32_t offset) const
 {
     uint32_t lineIndex = lineIndexOf(offset);
     uint32_t lineStartOffset = lineStartOffsets_[lineIndex];
     MOZ_ASSERT(offset >= lineStartOffset);
     return offset - lineStartOffset;
 }
 
 void
-TokenStream::SourceCoords::lineNumAndColumnIndex(uint32_t offset, uint32_t* lineNum,
+SourceCoords::lineNumAndColumnIndex(uint32_t offset, uint32_t* lineNum,
                                                  uint32_t* columnIndex) const
 {
     uint32_t lineIndex = lineIndexOf(offset);
     *lineNum = lineIndexToNum(lineIndex);
     uint32_t lineStartOffset = lineStartOffsets_[lineIndex];
     MOZ_ASSERT(offset >= lineStartOffset);
     *columnIndex = offset - lineStartOffset;
 }
@@ -445,17 +446,18 @@ TokenStream::TokenStream(JSContext* cx, 
 #endif
 
 bool
 TokenStream::checkOptions()
 {
     // Constrain starting columns to half of the range of a signed 32-bit value,
     // to avoid overflow.
     if (options().column >= mozilla::MaxValue<int32_t>::value / 2 + 1) {
-        reportErrorNoOffset(JSMSG_BAD_COLUMN_NUMBER);
+        OffsetErrorContext context = noOffset();
+        mozilla::Unused << ReportTokenError(&context, JSMSG_BAD_COLUMN_NUMBER);
         return false;
     }
 
     return true;
 }
 
 TokenStream::~TokenStream()
 {
@@ -661,32 +663,16 @@ bool
 TokenStream::seek(const Position& pos, const TokenStream& other)
 {
     if (!srcCoords.fill(other.srcCoords))
         return false;
     seek(pos);
     return true;
 }
 
-bool
-TokenStream::reportStrictModeErrorNumberVA(UniquePtr<JSErrorNotes> notes, uint32_t offset,
-                                           bool strictMode, unsigned errorNumber, va_list args)
-{
-    // In strict mode code, this is an error, not merely a warning.
-    unsigned flags;
-    if (strictMode)
-        flags = JSREPORT_ERROR;
-    else if (options().extraWarningsOption)
-        flags = JSREPORT_WARNING | JSREPORT_STRICT;
-    else
-        return true;
-
-    return reportCompileErrorNumberVA(Move(notes), offset, flags, errorNumber, args);
-}
-
 void
 CompileError::throwError(JSContext* cx)
 {
     if (JSREPORT_IS_WARNING(flags)) {
         CallWarningReporter(cx, this);
         return;
     }
 
@@ -699,217 +685,16 @@ CompileError::throwError(JSContext* cx)
     // reporter is to ignore a report with this flag for all but top-level
     // compilation errors.  The exception will remain pending, and so long
     // as the non-top-level "load", "eval", or "compile" native function
     // returns false, the top-level reporter will eventually receive the
     // uncaught exception report.
     ErrorToException(cx, this, nullptr, nullptr);
 }
 
-bool
-TokenStream::reportCompileErrorNumberVA(UniquePtr<JSErrorNotes> notes, uint32_t offset,
-                                        unsigned flags, unsigned errorNumber, va_list args)
-{
-    bool warning = JSREPORT_IS_WARNING(flags);
-
-    if (warning && options().werrorOption) {
-        flags &= ~JSREPORT_WARNING;
-        warning = false;
-    }
-
-    // On the active thread, report the error immediately. When compiling off
-    // thread, save the error so that the thread finishing the parse can report
-    // it later.
-    CompileError tempErr;
-    CompileError* tempErrPtr = &tempErr;
-    if (cx->helperThread() && !cx->addPendingCompileError(&tempErrPtr))
-        return false;
-    CompileError& err = *tempErrPtr;
-
-    err.notes = Move(notes);
-    err.flags = flags;
-    err.errorNumber = errorNumber;
-    err.filename = filename;
-    err.isMuted = mutedErrors;
-    if (offset == NoOffset) {
-        err.lineno = 0;
-        err.column = 0;
-    } else {
-        err.lineno = srcCoords.lineNum(offset);
-        err.column = srcCoords.columnIndex(offset);
-    }
-
-    // If we have no location information, try to get one from the caller.
-    bool callerFilename = false;
-    if (offset != NoOffset && !err.filename && !cx->helperThread()) {
-        NonBuiltinFrameIter iter(cx,
-                                 FrameIter::FOLLOW_DEBUGGER_EVAL_PREV_LINK,
-                                 cx->compartment()->principals());
-        if (!iter.done() && iter.filename()) {
-            callerFilename = true;
-            err.filename = iter.filename();
-            err.lineno = iter.computeLine(&err.column);
-        }
-    }
-
-    if (!ExpandErrorArgumentsVA(cx, GetErrorMessage, nullptr, errorNumber,
-                                nullptr, ArgumentsAreLatin1, &err, args))
-    {
-        return false;
-    }
-
-    // Given a token, T, that we want to complain about: if T's (starting)
-    // lineno doesn't match TokenStream's lineno, that means we've scanned past
-    // the line that T starts on, which makes it hard to print some or all of
-    // T's (starting) line for context.
-    //
-    // So we don't even try, leaving report.linebuf and friends zeroed.  This
-    // means that any error involving a multi-line token (e.g. an unterminated
-    // multi-line string literal) won't have a context printed.
-    if (offset != NoOffset && err.lineno == lineno && !callerFilename) {
-        // We show only a portion (a "window") of the line around the erroneous
-        // token -- the first char in the token, plus |windowRadius| chars
-        // before it and |windowRadius - 1| chars after it.  This is because
-        // lines can be very long and printing the whole line is (a) not that
-        // helpful, and (b) can waste a lot of memory.  See bug 634444.
-        static const size_t windowRadius = 60;
-
-        // The window must start within the current line, no earlier than
-        // windowRadius characters before offset.
-        size_t windowStart = (offset - linebase > windowRadius) ?
-                             offset - windowRadius :
-                             linebase;
-
-        // The window must start within the portion of the current line
-        // that we actually have in our buffer.
-        if (windowStart < userbuf.startOffset())
-            windowStart = userbuf.startOffset();
-
-        // The window must end within the current line, no later than
-        // windowRadius after offset.
-        size_t windowEnd = userbuf.findEOLMax(offset, windowRadius);
-        size_t windowLength = windowEnd - windowStart;
-        MOZ_ASSERT(windowLength <= windowRadius * 2);
-
-        // Create the windowed strings.
-        StringBuffer windowBuf(cx);
-        if (!windowBuf.append(userbuf.rawCharPtrAt(windowStart), windowLength) ||
-            !windowBuf.append('\0'))
-        {
-            return false;
-        }
-
-        // The window into the offending source line, without final \n.
-        UniqueTwoByteChars linebuf(windowBuf.stealChars());
-        if (!linebuf)
-            return false;
-
-        err.initOwnedLinebuf(linebuf.release(), windowLength, offset - windowStart);
-    }
-
-    if (!cx->helperThread())
-        err.throwError(cx);
-
-    return warning;
-}
-
-bool
-TokenStream::reportStrictModeError(unsigned errorNumber, ...)
-{
-    va_list args;
-    va_start(args, errorNumber);
-    bool result = reportStrictModeErrorNumberVA(nullptr, currentToken().pos.begin, strictMode(),
-                                                errorNumber, args);
-    va_end(args);
-    return result;
-}
-
-bool
-TokenStream::reportError(unsigned errorNumber, ...)
-{
-    va_list args;
-    va_start(args, errorNumber);
-    bool result = reportCompileErrorNumberVA(nullptr, currentToken().pos.begin, JSREPORT_ERROR,
-                                             errorNumber, args);
-    va_end(args);
-    return result;
-}
-
-bool
-TokenStream::reportErrorNoOffset(unsigned errorNumber, ...)
-{
-    va_list args;
-    va_start(args, errorNumber);
-    bool result = reportCompileErrorNumberVA(nullptr, NoOffset, JSREPORT_ERROR,
-                                             errorNumber, args);
-    va_end(args);
-    return result;
-}
-
-bool
-TokenStream::warning(unsigned errorNumber, ...)
-{
-    va_list args;
-    va_start(args, errorNumber);
-    bool result = reportCompileErrorNumberVA(nullptr, currentToken().pos.begin, JSREPORT_WARNING,
-                                             errorNumber, args);
-    va_end(args);
-    return result;
-}
-
-bool
-TokenStream::reportExtraWarningErrorNumberVA(UniquePtr<JSErrorNotes> notes, uint32_t offset,
-                                             unsigned errorNumber, va_list args)
-{
-    if (!options().extraWarningsOption)
-        return true;
-
-    return reportCompileErrorNumberVA(Move(notes), offset, JSREPORT_STRICT|JSREPORT_WARNING,
-                                      errorNumber, args);
-}
-
-void
-TokenStream::reportAsmJSError(uint32_t offset, unsigned errorNumber, ...)
-{
-    va_list args;
-    va_start(args, errorNumber);
-    unsigned flags = options().throwOnAsmJSValidationFailureOption
-                     ? JSREPORT_ERROR
-                     : JSREPORT_WARNING;
-    reportCompileErrorNumberVA(nullptr, offset, flags, errorNumber, args);
-    va_end(args);
-}
-
-void
-TokenStream::error(unsigned errorNumber, ...)
-{
-    va_list args;
-    va_start(args, errorNumber);
-#ifdef DEBUG
-    bool result =
-#endif
-        reportCompileErrorNumberVA(nullptr, currentToken().pos.begin, JSREPORT_ERROR,
-                                   errorNumber, args);
-    MOZ_ASSERT(!result, "reporting an error returned true?");
-    va_end(args);
-}
-
-void
-TokenStream::errorAt(uint32_t offset, unsigned errorNumber, ...)
-{
-    va_list args;
-    va_start(args, errorNumber);
-#ifdef DEBUG
-    bool result =
-#endif
-        reportCompileErrorNumberVA(nullptr, offset, JSREPORT_ERROR, errorNumber, args);
-    MOZ_ASSERT(!result, "reporting an error returned true?");
-    va_end(args);
-}
-
 // We have encountered a '\': check for a Unicode escape sequence after it.
 // Return the length of the escape sequence and the character code point (by
 // value) if we found a Unicode escape sequence.  Otherwise, return 0.  In both
 // cases, do not advance along the buffer.
 uint32_t
 TokenStream::peekUnicodeEscape(uint32_t* codePoint)
 {
     int32_t c = getCharIgnoreEOL();
@@ -1037,17 +822,17 @@ TokenStream::getDirective(bool isMultili
                           const char* errorMsgPragma,
                           UniqueTwoByteChars* destination)
 {
     MOZ_ASSERT(directiveLength <= 18);
     char16_t peeked[18];
 
     if (peekChars(directiveLength, peeked) && CharsMatch(peeked, directive)) {
         if (shouldWarnDeprecated) {
-            if (!warning(JSMSG_DEPRECATED_PRAGMA, errorMsgPragma))
+            if (!ReportWarning(this, JSMSG_DEPRECATED_PRAGMA, errorMsgPragma))
                 return false;
         }
 
         skipChars(directiveLength);
         tokenbuf.clear();
 
         do {
             int32_t c;
@@ -1490,37 +1275,37 @@ TokenStream::getTokenInternal(TokenKind*
         }
         if (c == 'e' || c == 'E') {
             hasExp = true;
             c = getCharIgnoreEOL();
             if (c == '+' || c == '-')
                 c = getCharIgnoreEOL();
             if (!JS7_ISDEC(c)) {
                 ungetCharIgnoreEOL(c);
-                reportError(JSMSG_MISSING_EXPONENT);
+                mozilla::Unused << ReportTokenError(this, JSMSG_MISSING_EXPONENT);
                 goto error;
             }
             do {
                 c = getCharIgnoreEOL();
             } while (JS7_ISDEC(c));
         }
         ungetCharIgnoreEOL(c);
 
         if (c != EOF) {
             if (unicode::IsIdentifierStart(char16_t(c))) {
-                reportError(JSMSG_IDSTART_AFTER_NUMBER);
+                mozilla::Unused << ReportTokenError(this, JSMSG_IDSTART_AFTER_NUMBER);
                 goto error;
             }
 
             if (MOZ_UNLIKELY(unicode::IsLeadSurrogate(c))) {
                 uint32_t codePoint;
                 if (matchTrailForLeadSurrogate(c, nullptr, &codePoint) &&
                     unicode::IsIdentifierStart(codePoint))
                 {
-                    reportError(JSMSG_IDSTART_AFTER_NUMBER);
+                    mozilla::Unused << ReportTokenError(this, JSMSG_IDSTART_AFTER_NUMBER);
                     goto error;
                 }
             }
         }
 
         // Unlike identifiers and strings, numbers cannot contain escaped
         // chars, so we don't need to use tokenbuf.  Instead we can just
         // convert the char16_t characters in userbuf to the numeric value.
@@ -1563,84 +1348,84 @@ TokenStream::getTokenInternal(TokenKind*
         tp = newToken(-1);
         int radix;
         c = getCharIgnoreEOL();
         if (c == 'x' || c == 'X') {
             radix = 16;
             c = getCharIgnoreEOL();
             if (!JS7_ISHEX(c)) {
                 ungetCharIgnoreEOL(c);
-                reportError(JSMSG_MISSING_HEXDIGITS);
+                mozilla::Unused << ReportTokenError(this, JSMSG_MISSING_HEXDIGITS);
                 goto error;
             }
             numStart = userbuf.addressOfNextRawChar() - 1;  // one past the '0x'
             while (JS7_ISHEX(c))
                 c = getCharIgnoreEOL();
         } else if (c == 'b' || c == 'B') {
             radix = 2;
             c = getCharIgnoreEOL();
             if (c != '0' && c != '1') {
                 ungetCharIgnoreEOL(c);
-                reportError(JSMSG_MISSING_BINARY_DIGITS);
+                mozilla::Unused << ReportTokenError(this, JSMSG_MISSING_BINARY_DIGITS);
                 goto error;
             }
             numStart = userbuf.addressOfNextRawChar() - 1;  // one past the '0b'
             while (c == '0' || c == '1')
                 c = getCharIgnoreEOL();
         } else if (c == 'o' || c == 'O') {
             radix = 8;
             c = getCharIgnoreEOL();
             if (c < '0' || c > '7') {
                 ungetCharIgnoreEOL(c);
-                reportError(JSMSG_MISSING_OCTAL_DIGITS);
+                mozilla::Unused << ReportTokenError(this, JSMSG_MISSING_OCTAL_DIGITS);
                 goto error;
             }
             numStart = userbuf.addressOfNextRawChar() - 1;  // one past the '0o'
             while ('0' <= c && c <= '7')
                 c = getCharIgnoreEOL();
         } else if (JS7_ISDEC(c)) {
             radix = 8;
             numStart = userbuf.addressOfNextRawChar() - 1;  // one past the '0'
             while (JS7_ISDEC(c)) {
                 // Octal integer literals are not permitted in strict mode code.
-                if (!reportStrictModeError(JSMSG_DEPRECATED_OCTAL))
+                if (!ReportStrictModeTokenError(this, JSMSG_DEPRECATED_OCTAL))
                     goto error;
 
                 // Outside strict mode, we permit 08 and 09 as decimal numbers,
                 // which makes our behaviour a superset of the ECMA numeric
                 // grammar. We might not always be so permissive, so we warn
                 // about it.
                 if (c >= '8') {
-                    if (!warning(JSMSG_BAD_OCTAL, c == '8' ? "08" : "09"))
+                    if (!ReportWarning(this, JSMSG_BAD_OCTAL, c == '8' ? "08" : "09"))
                         goto error;
 
                     // Use the decimal scanner for the rest of the number.
                     goto decimal;
                 }
                 c = getCharIgnoreEOL();
             }
         } else {
             // '0' not followed by 'x', 'X' or a digit;  scan as a decimal number.
             numStart = userbuf.addressOfNextRawChar() - 1;
             goto decimal;
         }
         ungetCharIgnoreEOL(c);
 
         if (c != EOF) {
             if (unicode::IsIdentifierStart(char16_t(c))) {
-                reportError(JSMSG_IDSTART_AFTER_NUMBER);
+                mozilla::Unused << ReportTokenError(this, JSMSG_IDSTART_AFTER_NUMBER);
                 goto error;
             }
 
             if (MOZ_UNLIKELY(unicode::IsLeadSurrogate(c))) {
                 uint32_t codePoint;
                 if (matchTrailForLeadSurrogate(c, nullptr, &codePoint) &&
                     unicode::IsIdentifierStart(codePoint))
                 {
-                    reportError(JSMSG_IDSTART_AFTER_NUMBER);
+                    mozilla::Unused << ReportTokenError(this, JSMSG_IDSTART_AFTER_NUMBER);
                     goto error;
                 }
             }
         }
 
         double dval;
         const char16_t* dummy;
         if (!GetPrefixInteger(cx, numStart, userbuf.addressOfNextRawChar(), radix, &dummy, &dval))
@@ -1787,17 +1572,17 @@ TokenStream::getTokenInternal(TokenKind*
                    !(c == '*' && matchChar('/'))) {
                 if (c == '@' || c == '#') {
                     bool shouldWarn = c == '@';
                     if (!getDirectives(true, shouldWarn))
                         goto error;
                 }
             }
             if (c == EOF) {
-                reportError(JSMSG_UNTERMINATED_COMMENT);
+                mozilla::Unused << ReportTokenError(this, JSMSG_UNTERMINATED_COMMENT);
                 goto error;
             }
             if (linenoBefore != lineno)
                 updateFlagsForEOL();
             cursor = (cursor - 1) & ntokensMask;
             goto retry;
         }
 
@@ -1817,17 +1602,17 @@ TokenStream::getTokenInternal(TokenKind*
                 } else if (c == ']') {
                     inCharClass = false;
                 } else if (c == '/' && !inCharClass) {
                     // For compat with IE, allow unescaped / in char classes.
                     break;
                 }
                 if (c == '\n' || c == EOF) {
                     ungetChar(c);
-                    reportError(JSMSG_UNTERMINATED_REGEXP);
+                    mozilla::Unused << ReportTokenError(this, JSMSG_UNTERMINATED_REGEXP);
                     goto error;
                 }
                 if (!tokenbuf.append(c))
                     goto error;
             }
 
             RegExpFlag reflags = NoFlags;
             unsigned length = tokenbuf.length() + 1;
@@ -1851,17 +1636,17 @@ TokenStream::getTokenInternal(TokenKind*
             }
 
             if (!peekChar(&c))
                 goto error;
             if (JS7_ISLET(c)) {
                 char buf[2] = { '\0', '\0' };
                 tp->pos.begin += length + 1;
                 buf[0] = char(c);
-                reportError(JSMSG_BAD_REGEXP_FLAG, buf);
+                mozilla::Unused << ReportTokenError(this, JSMSG_BAD_REGEXP_FLAG, buf);
                 (void) getChar();
                 goto error;
             }
             tp->type = TOK_REGEXP;
             tp->setRegExpFlags(reflags);
             goto out;
         }
 
@@ -1886,17 +1671,17 @@ TokenStream::getTokenInternal(TokenKind*
             tp->type = TOK_DEC;
         } else {
             tp->type = matchChar('=') ? TOK_SUBASSIGN : TOK_SUB;
         }
         goto out;
 
       badchar:
       default:
-        reportError(JSMSG_ILLEGAL_CHARACTER);
+        mozilla::Unused << ReportTokenError(this, JSMSG_ILLEGAL_CHARACTER);
         goto error;
     }
 
     MOZ_CRASH("should have jumped to |out| or |error|");
 
   out:
     if (flags.hitOOM)
         return false;
@@ -1928,16 +1713,26 @@ TokenStream::getTokenInternal(TokenKind*
     // because the parser will deal with the illegal token by aborting parsing
     // immediately.
     userbuf.poison();
 #endif
     MOZ_MAKE_MEM_UNDEFINED(ttp, sizeof(*ttp));
     return false;
 }
 
+bool TokenStream::checkForInvalidTemplateEscapeError()
+{
+   if (invalidTemplateEscapeType == InvalidEscapeType::None)
+       return true;
+
+   OffsetErrorContext context = atOffset(invalidTemplateEscapeOffset);
+   ReportInvalidEscapeError(&context, invalidTemplateEscapeType);
+   return false;
+}
+
 bool
 TokenStream::getStringOrTemplateToken(int untilChar, Token** tp)
 {
     int c;
     int nc = -1;
 
     bool parsingTemplate = (untilChar == '`');
 
@@ -1945,17 +1740,17 @@ TokenStream::getStringOrTemplateToken(in
     tokenbuf.clear();
 
     // We need to detect any of these chars:  " or ', \n (or its
     // equivalents), \\, EOF.  Because we detect EOL sequences here and
     // put them back immediately, we can use getCharIgnoreEOL().
     while ((c = getCharIgnoreEOL()) != untilChar) {
         if (c == EOF) {
             ungetCharIgnoreEOL(c);
-            error(JSMSG_UNTERMINATED_STRING);
+            IgnoreReportTokenError(this, JSMSG_UNTERMINATED_STRING);
             return false;
         }
 
         if (c == '\\') {
             // When parsing templates, we don't immediately report errors for
             // invalid escapes; these are handled by the parser.
             // In those cases we don't append to tokenbuf, since it won't be
             // read.
@@ -1990,54 +1785,58 @@ TokenStream::getStringOrTemplateToken(in
                     do {
                         int32_t c = getCharIgnoreEOL();
                         if (c == EOF) {
                             if (parsingTemplate) {
                                 setInvalidTemplateEscape(start, InvalidEscapeType::Unicode);
                                 valid = false;
                                 break;
                             }
-                            reportInvalidEscapeError(start, InvalidEscapeType::Unicode);
+                            OffsetErrorContext context = atOffset(start);
+                            ReportInvalidEscapeError(&context, InvalidEscapeType::Unicode);
                             return false;
                         }
                         if (c == '}') {
                             if (first) {
                                 if (parsingTemplate) {
                                     setInvalidTemplateEscape(start, InvalidEscapeType::Unicode);
                                     valid = false;
                                     break;
                                 }
-                                reportInvalidEscapeError(start, InvalidEscapeType::Unicode);
+                                OffsetErrorContext context = atOffset(start);
+                                ReportInvalidEscapeError(&context, InvalidEscapeType::Unicode);
                                 return false;
                             }
                             break;
                         }
 
                         if (!JS7_ISHEX(c)) {
                             if (parsingTemplate) {
                                 // We put the character back so that we read
                                 // it on the next pass, which matters if it
                                 // was '`' or '\'.
                                 ungetCharIgnoreEOL(c);
                                 setInvalidTemplateEscape(start, InvalidEscapeType::Unicode);
                                 valid = false;
                                 break;
                             }
-                            reportInvalidEscapeError(start, InvalidEscapeType::Unicode);
+                            OffsetErrorContext context = atOffset(start);
+                            ReportInvalidEscapeError(&context, InvalidEscapeType::Unicode);
                             return false;
                         }
 
                         code = (code << 4) | JS7_UNHEX(c);
                         if (code > unicode::NonBMPMax) {
                             if (parsingTemplate) {
                                 setInvalidTemplateEscape(start + 3, InvalidEscapeType::UnicodeOverflow);
                                 valid = false;
                                 break;
                             }
-                            reportInvalidEscapeError(start + 3, InvalidEscapeType::UnicodeOverflow);
+                            OffsetErrorContext context = atOffset(start + 3);
+                            ReportInvalidEscapeError(&context, InvalidEscapeType::UnicodeOverflow);
                             return false;
                         }
 
                         first = false;
                     } while (true);
 
                     if (!valid)
                         continue;
@@ -2062,17 +1861,18 @@ TokenStream::getStringOrTemplateToken(in
                     c = (c << 4) + JS7_UNHEX(cp[2]);
                     c = (c << 4) + JS7_UNHEX(cp[3]);
                     skipChars(4);
                 } else {
                     if (parsingTemplate) {
                         setInvalidTemplateEscape(start, InvalidEscapeType::Unicode);
                         continue;
                     }
-                    reportInvalidEscapeError(start, InvalidEscapeType::Unicode);
+                    OffsetErrorContext context = atOffset(start);
+                    ReportInvalidEscapeError(&context, InvalidEscapeType::Unicode);
                     return false;
                 }
                 break;
               }
 
               // Hexadecimal character specification.
               case 'x': {
                 char16_t cp[2];
@@ -2080,17 +1880,18 @@ TokenStream::getStringOrTemplateToken(in
                     c = (JS7_UNHEX(cp[0]) << 4) + JS7_UNHEX(cp[1]);
                     skipChars(2);
                 } else {
                     uint32_t start = userbuf.offset() - 2;
                     if (parsingTemplate) {
                         setInvalidTemplateEscape(start, InvalidEscapeType::Hexadecimal);
                         continue;
                     }
-                    reportInvalidEscapeError(start, InvalidEscapeType::Hexadecimal);
+                    OffsetErrorContext context = atOffset(start);
+                    ReportInvalidEscapeError(&context, InvalidEscapeType::Hexadecimal);
                     return false;
                 }
                 break;
               }
 
               default:
                 // Octal character specification.
                 if (JS7_ISOCT(c)) {
@@ -2100,17 +1901,17 @@ TokenStream::getStringOrTemplateToken(in
                         return false;
 
                     // Strict mode code allows only \0, then a non-digit.
                     if (val != 0 || JS7_ISDEC(c)) {
                         if (parsingTemplate) {
                             setInvalidTemplateEscape(userbuf.offset() - 2, InvalidEscapeType::Octal);
                             continue;
                         }
-                        if (!reportStrictModeError(JSMSG_DEPRECATED_OCTAL))
+                        if (!ReportStrictModeTokenError(this, JSMSG_DEPRECATED_OCTAL))
                             return false;
                         flags.sawOctalEscape = true;
                     }
 
                     if (JS7_ISOCT(c)) {
                         val = 8 * val + JS7_UNOCT(c);
                         getChar();
                         if (!peekChar(&c))
@@ -2127,17 +1928,17 @@ TokenStream::getStringOrTemplateToken(in
 
                     c = char16_t(val);
                 }
                 break;
             }
         } else if (TokenBuf::isRawEOLChar(c)) {
             if (!parsingTemplate) {
                 ungetCharIgnoreEOL(c);
-                error(JSMSG_UNTERMINATED_STRING);
+                IgnoreReportTokenError(this, JSMSG_UNTERMINATED_STRING);
                 return false;
             }
             if (c == '\r') {
                 c = '\n';
                 if (userbuf.peekRawChar() == '\n')
                     skipCharsIgnoreEOL(1);
             }
             updateLineInfoForEOL();
@@ -2166,16 +1967,62 @@ TokenStream::getStringOrTemplateToken(in
         else
             (*tp)->type = TOK_NO_SUBS_TEMPLATE;
     }
 
     (*tp)->setAtom(atom);
     return true;
 }
 
+bool
+TokenStream::fillComplaint(JSErrorReport& err) {
+    const uint32_t offset = currentPosition();
+
+    // We show only a portion (a "window") of the line around the erroneous
+    // token -- the first char in the token, plus |windowRadius| chars
+    // before it and |windowRadius - 1| chars after it.  This is because
+    // lines can be very long and printing the whole line is (a) not that
+    // helpful, and (b) can waste a lot of memory.  See bug 634444.
+    static const size_t windowRadius = 60;
+
+
+    // The window must start within the current line, no earlier than
+    // windowRadius characters before offset.
+    size_t windowStart = (offset - linebase > windowRadius) ?
+                         offset - windowRadius :
+                         linebase;
+
+    // The window must start within the portion of the current line
+    // that we actually have in our buffer.
+    if (windowStart < userbuf.startOffset())
+        windowStart = userbuf.startOffset();
+
+    // The window must end within the current line, no later than
+    // windowRadius after offset.
+    size_t windowEnd = userbuf.findEOLMax(offset, windowRadius);
+    size_t windowLength = windowEnd - windowStart;
+    MOZ_ASSERT(windowLength <= windowRadius * 2);
+
+    // Create the windowed strings.
+    StringBuffer windowBuf(cx);
+    if (!windowBuf.append(userbuf.rawCharPtrAt(windowStart), windowLength)
+        || !windowBuf.append('\0'))
+    {
+        return false;
+    }
+
+    // The window into the offending source line, without final \n.
+    UniqueTwoByteChars linebuf(windowBuf.stealChars());
+    if (!linebuf)
+        return false;
+
+    err.initOwnedLinebuf(linebuf.release(), windowLength, offset - windowStart);
+    return true;
+}
+
 JS_FRIEND_API(int)
 js_fgets(char* buf, int size, FILE* file)
 {
     int n, i, c;
     bool crflag;
 
     n = size - 1;
     if (n < 0)
--- a/js/src/frontend/TokenStream.h
+++ b/js/src/frontend/TokenStream.h
@@ -17,16 +17,18 @@
 
 #include <stdarg.h>
 #include <stddef.h>
 #include <stdio.h>
 
 #include "jscntxt.h"
 #include "jspubtd.h"
 
+#include "frontend/ErrorReport.h"
+#include "frontend/SourceCoords.h"
 #include "frontend/TokenKind.h"
 #include "js/UniquePtr.h"
 #include "js/Vector.h"
 #include "vm/RegExpObject.h"
 #include "vm/String.h"
 
 struct KeywordInfo;
 
@@ -74,30 +76,16 @@ struct TokenPos {
 
     bool encloses(const TokenPos& pos) const {
         return begin <= pos.begin && pos.end <= end;
     }
 };
 
 enum DecimalPoint { NoDecimal = false, HasDecimal = true };
 
-enum class InvalidEscapeType {
-    // No invalid character escapes.
-    None,
-    // A malformed \x escape.
-    Hexadecimal,
-    // A malformed \u escape.
-    Unicode,
-    // An otherwise well-formed \u escape which represents a
-    // codepoint > 10FFFF.
-    UnicodeOverflow,
-    // An octal escape in a template token.
-    Octal
-};
-
 class TokenStream;
 
 struct Token
 {
   private:
     // Sometimes the parser needs to inform the tokenizer to interpret
     // subsequent text in a particular manner: for example, to tokenize a
     // keyword as an identifier, not as the actual keyword, on the right-hand
@@ -304,17 +292,17 @@ class StrictModeGetter {
 // this turns out not to be a problem in practice. See the
 // mozilla.dev.tech.js-engine.internals thread entitled 'Bug in the scanner?'
 // for more details:
 // https://groups.google.com/forum/?fromgroups=#!topic/mozilla.dev.tech.js-engine.internals/2JLH5jRcr7E).
 //
 // The methods seek() and tell() allow to rescan from a previous visited
 // location of the buffer.
 //
-class MOZ_STACK_CLASS TokenStream
+class MOZ_STACK_CLASS TokenStream: public ErrorContext
 {
     // Unicode separators that are treated as line terminators, in addition to \n, \r.
     enum {
         LINE_SEPARATOR = 0x2028,
         PARA_SEPARATOR = 0x2029
     };
 
     static const size_t ntokens = 4;                // 1 current + 2 lookahead, rounded
@@ -333,18 +321,19 @@ class MOZ_STACK_CLASS TokenStream
     MOZ_MUST_USE bool checkOptions();
 
     // Accessors.
     const Token& currentToken() const { return tokens[cursor]; }
     bool isCurrentTokenType(TokenKind type) const {
         return currentToken().type == type;
     }
     const CharBuffer& getTokenbuf() const { return tokenbuf; }
-    const char* getFilename() const { return filename; }
+    const char* getFilename() const override final { return filename; }
     bool getMutedErrors() const { return mutedErrors; }
+    virtual bool hasMutedErrors() const override final { return getMutedErrors(); }
     JSVersion versionNumber() const { return VersionNumber(options().version); }
     JSVersion versionWithFlags() const { return options().version; }
 
   private:
     PropertyName* reservedWordToPropertyName(TokenKind tt) const;
 
   public:
     PropertyName* currentName() const {
@@ -377,51 +366,21 @@ class MOZ_STACK_CLASS TokenStream
         return invalidTemplateEscapeType != InvalidEscapeType::None;
     }
     void clearInvalidTemplateEscape() {
         invalidTemplateEscapeType = InvalidEscapeType::None;
     }
 
     // If there is an invalid escape in a template, report it and return false,
     // otherwise return true.
-    bool checkForInvalidTemplateEscapeError() {
-        if (invalidTemplateEscapeType == InvalidEscapeType::None)
-            return true;
-
-        reportInvalidEscapeError(invalidTemplateEscapeOffset, invalidTemplateEscapeType);
-        return false;
-    }
-
-    // TokenStream-specific error reporters.
-    bool reportError(unsigned errorNumber, ...);
-    bool reportErrorNoOffset(unsigned errorNumber, ...);
-
-    // Report the given error at the current offset.
-    void error(unsigned errorNumber, ...);
-
-    // Report the given error at the given offset.
-    void errorAt(uint32_t offset, unsigned errorNumber, ...);
+    bool checkForInvalidTemplateEscapeError();
 
-    // Warn at the current offset.
-    MOZ_MUST_USE bool warning(unsigned errorNumber, ...);
-
-    static const uint32_t NoOffset = UINT32_MAX;
-
-    // General-purpose error reporters.  You should avoid calling these
-    // directly, and instead use the more succinct alternatives (error(),
-    // warning(), &c.) in TokenStream, Parser, and BytecodeEmitter.
-    bool reportCompileErrorNumberVA(UniquePtr<JSErrorNotes> notes, uint32_t offset, unsigned flags,
-                                    unsigned errorNumber, va_list args);
-    bool reportStrictModeErrorNumberVA(UniquePtr<JSErrorNotes> notes, uint32_t offset,
-                                       bool strictMode, unsigned errorNumber, va_list args);
-    bool reportExtraWarningErrorNumberVA(UniquePtr<JSErrorNotes> notes, uint32_t offset,
-                                         unsigned errorNumber, va_list args);
-
-    // asm.js reporter
-    void reportAsmJSError(uint32_t offset, unsigned errorNumber, ...);
+    uint32_t currentPosition() const override final {
+        return currentToken().pos.begin;
+    }
 
     JSAtom* getRawTemplateStringAtom() {
         MOZ_ASSERT(currentToken().type == TOK_TEMPLATE_HEAD ||
                    currentToken().type == TOK_NO_SUBS_TEMPLATE);
         const char16_t* cur = userbuf.rawCharPtrAt(currentToken().pos.begin + 1);
         const char16_t* end;
         if (currentToken().type == TOK_TEMPLATE_HEAD) {
             // Of the form    |`...${|   or   |}...${|
@@ -441,48 +400,28 @@ class MOZ_STACK_CLASS TokenStream
             }
             if (!charbuf.append(ch))
                 return nullptr;
             cur++;
         }
         return AtomizeChars(cx, charbuf.begin(), charbuf.length());
     }
 
+    bool strictMode() const override final {
+        return strictModeGetter && strictModeGetter->strictMode();
+    }
+
   private:
-    // These are private because they should only be called by the tokenizer
-    // while tokenizing not by, for example, BytecodeEmitter.
-    bool reportStrictModeError(unsigned errorNumber, ...);
-    bool strictMode() const { return strictModeGetter && strictModeGetter->strictMode(); }
-
     void setInvalidTemplateEscape(uint32_t offset, InvalidEscapeType type) {
         MOZ_ASSERT(type != InvalidEscapeType::None);
         if (invalidTemplateEscapeType != InvalidEscapeType::None)
             return;
         invalidTemplateEscapeOffset = offset;
         invalidTemplateEscapeType = type;
     }
-    void reportInvalidEscapeError(uint32_t offset, InvalidEscapeType type) {
-        switch (type) {
-            case InvalidEscapeType::None:
-                MOZ_ASSERT_UNREACHABLE("unexpected InvalidEscapeType");
-                return;
-            case InvalidEscapeType::Hexadecimal:
-                errorAt(offset, JSMSG_MALFORMED_ESCAPE, "hexadecimal");
-                return;
-            case InvalidEscapeType::Unicode:
-                errorAt(offset, JSMSG_MALFORMED_ESCAPE, "Unicode");
-                return;
-            case InvalidEscapeType::UnicodeOverflow:
-                errorAt(offset, JSMSG_UNICODE_OVERFLOW, "escape sequence");
-                return;
-            case InvalidEscapeType::Octal:
-                errorAt(offset, JSMSG_DEPRECATED_OCTAL);
-                return;
-        }
-    }
 
     static JSAtom* atomize(JSContext* cx, CharBuffer& cb);
     MOZ_MUST_USE bool putIdentInTokenbuf(const char16_t* identStart);
 
     struct Flags
     {
         bool isEOF:1;           // Hit end of file.
         bool isDirtyLine:1;     // Non-whitespace since start of line.
@@ -643,17 +582,17 @@ class MOZ_STACK_CLASS TokenStream
         // 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) {
             bool onThisLine;
             if (!srcCoords.isOnThisLine(curr.pos.end, lineno, &onThisLine))
-                return reportError(JSMSG_OUT_OF_MEMORY);
+                return ReportTokenError(this, JSMSG_OUT_OF_MEMORY);
             if (onThisLine) {
                 MOZ_ASSERT(!flags.hadError);
                 verifyConsistentModifier(modifier, nextToken());
                 *ttp = nextToken().type;
                 return true;
             }
         }
 
@@ -757,97 +696,36 @@ class MOZ_STACK_CLASS TokenStream
     bool hasSourceMapURL() const {
         return sourceMapURL_ != nullptr;
     }
 
     char16_t* sourceMapURL() {
         return sourceMapURL_.get();
     }
 
-    // This class maps a userbuf offset (which is 0-indexed) to a line number
-    // (which is 1-indexed) and a column index (which is 0-indexed).
-    class SourceCoords
-    {
-        // For a given buffer holding source code, |lineStartOffsets_| has one
-        // element per line of source code, plus one sentinel element.  Each
-        // non-sentinel element holds the buffer offset for the start of the
-        // corresponding line of source code.  For this example script:
-        //
-        // 1  // xyz            [line starts at offset 0]
-        // 2  var x;            [line starts at offset 7]
-        // 3                    [line starts at offset 14]
-        // 4  var y;            [line starts at offset 15]
-        //
-        // |lineStartOffsets_| is:
-        //
-        //   [0, 7, 14, 15, MAX_PTR]
-        //
-        // To convert a "line number" to a "line index" (i.e. an index into
-        // |lineStartOffsets_|), subtract |initialLineNum_|.  E.g. line 3's
-        // line index is (3 - initialLineNum_), which is 2.  Therefore
-        // lineStartOffsets_[2] holds the buffer offset for the start of line 3,
-        // which is 14.  (Note that |initialLineNum_| is often 1, but not
-        // always.)
-        //
-        // The first element is always 0, and the last element is always the
-        // MAX_PTR sentinel.
-        //
-        // offset-to-line/column lookups are O(log n) in the worst case (binary
-        // search), but in practice they're heavily clustered and we do better
-        // than that by using the previous lookup's result (lastLineIndex_) as
-        // a starting point.
-        //
-        // Checking if an offset lies within a particular line number
-        // (isOnThisLine()) is O(1).
-        //
-        Vector<uint32_t, 128> lineStartOffsets_;
-        uint32_t            initialLineNum_;
+    SourceCoords srcCoords;
+    const SourceCoords& sourceCoords() const override final {
+        return srcCoords;
+    }
+    unsigned lineNumber() const override final {
+        return lineno;
+    }
 
-        // This is mutable because it's modified on every search, but that fact
-        // isn't visible outside this class.
-        mutable uint32_t    lastLineIndex_;
-
-        uint32_t lineIndexOf(uint32_t offset) const;
-
-        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(JSContext* cx, uint32_t ln);
-
-        MOZ_MUST_USE bool add(uint32_t lineNum, uint32_t lineStartOffset);
-        MOZ_MUST_USE bool fill(const SourceCoords& other);
-
-        bool isOnThisLine(uint32_t offset, uint32_t lineNum, bool* onThisLine) const {
-            uint32_t lineIndex = lineNumToIndex(lineNum);
-            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;
+    // Add an error message displaying the context of a syntax error.
+    bool fillComplaint(JSErrorReport& err) override final;
 
     JSAtomState& names() const {
         return cx->names();
     }
 
-    JSContext* context() const {
+    virtual JSContext* context() const override final {
         return cx;
     }
 
-    const ReadOnlyCompileOptions& options() const {
+    virtual const ReadOnlyCompileOptions& options() const override final {
         return options_;
     }
 
   private:
     // This is the low-level interface to the JS source code buffer.  It just
     // gets raw chars, basically.  TokenStreams functions are layered on top
     // and do some extra stuff like converting all EOL sequences to '\n',
     // tracking the line number, and setting |flags.isEOF|.  (The "raw" in "raw
--- a/js/src/irregexp/RegExpParser.cpp
+++ b/js/src/irregexp/RegExpParser.cpp
@@ -241,17 +241,17 @@ RegExpParser<CharT>::RegExpParser(fronte
     Advance();
 }
 
 template <typename CharT>
 RegExpTree*
 RegExpParser<CharT>::ReportError(unsigned errorNumber, const char* param /* = nullptr */)
 {
     gc::AutoSuppressGC suppressGC(ts.context());
-    ts.reportError(errorNumber, param);
+    mozilla::Unused << ReportTokenError(&ts, errorNumber, param);
     return nullptr;
 }
 
 template <typename CharT>
 void
 RegExpParser<CharT>::Advance()
 {
     if (next_pos_ < end_) {
--- a/js/src/moz.build
+++ b/js/src/moz.build
@@ -176,16 +176,17 @@ UNIFIED_SOURCES += [
     'builtin/WeakMapObject.cpp',
     'builtin/WeakSetObject.cpp',
     'devtools/sharkctl.cpp',
     'ds/Bitmap.cpp',
     'ds/LifoAlloc.cpp',
     'ds/MemoryProtectionExceptionHandler.cpp',
     'frontend/BytecodeCompiler.cpp',
     'frontend/BytecodeEmitter.cpp',
+    'frontend/ErrorReport.cpp',
     'frontend/FoldConstants.cpp',
     'frontend/NameFunctions.cpp',
     'frontend/ParseNode.cpp',
     'frontend/TokenStream.cpp',
     'gc/Allocator.cpp',
     'gc/AtomMarking.cpp',
     'gc/Barrier.cpp',
     'gc/GCTrace.cpp',
--- a/js/src/wasm/AsmJS.cpp
+++ b/js/src/wasm/AsmJS.cpp
@@ -43,16 +43,17 @@
 #include "wasm/WasmGenerator.h"
 #include "wasm/WasmInstance.h"
 #include "wasm/WasmJS.h"
 #include "wasm/WasmSerialize.h"
 #include "wasm/WasmValidate.h"
 
 #include "jsobjinlines.h"
 
+#include "frontend/ErrorReport.h"
 #include "frontend/ParseNode-inl.h"
 #include "vm/ArrayBufferObject-inl.h"
 
 using namespace js;
 using namespace js::frontend;
 using namespace js::jit;
 using namespace js::wasm;
 
@@ -1731,19 +1732,20 @@ class MOZ_STACK_CLASS ModuleValidator
         errorString_(nullptr),
         errorOffset_(UINT32_MAX),
         errorOverRecursed_(false)
     {}
 
     ~ModuleValidator() {
         if (errorString_) {
             MOZ_ASSERT(errorOffset_ != UINT32_MAX);
-            tokenStream().reportAsmJSError(errorOffset_,
-                                           JSMSG_USE_ASM_TYPE_FAIL,
-                                           errorString_.get());
+            OffsetErrorContext context = tokenStream().atOffset(errorOffset_);
+            ReportAsmJSError(&context,
+                             JSMSG_USE_ASM_TYPE_FAIL,
+                             errorString_.get());
         }
         if (errorOverRecursed_)
             ReportOverRecursed(cx_);
     }
 
     bool init() {
         asmJSMetadata_ = cx_->new_<AsmJSMetadata>();
         if (!asmJSMetadata_)