Bug 1377007 - Implementing basic binjs-ref parser in SpiderMonkey;r?jorendorff,arai draft
authorDavid Teller <dteller@mozilla.com>
Mon, 11 Sep 2017 16:54:48 +0200
changeset 711717 3ff1bdfc258dccdc41afd22d5dcf5e65dadb8270
parent 711716 cd5df80b791e61a0b11684d18ec3a318c2f380f9
child 711718 a0fc0a527337d29183a9544f06eb789bf2c0386a
push id93120
push userdteller@mozilla.com
push dateThu, 14 Dec 2017 15:55:11 +0000
reviewersjorendorff, arai
bugs1377007
milestone59.0a1
Bug 1377007 - Implementing basic binjs-ref parser in SpiderMonkey;r?jorendorff,arai This patch implements a Binary AST parser matching the latest binjs-ref parser at this date. The subset of JS recognized matches ES5, with an AST based on a slightly customized Babylon AST. At this stage, the parser trusts its input, insofar as it does not check directives or bindings. Followup patch will introduce checking of these directives/bindings. MozReview-Commit-ID: 1nt230rt02R
js/src/frontend/BinSource.cpp
js/src/frontend/BinSource.h
js/src/frontend/BinTokenReaderTester.cpp
js/src/frontend/BinTokenReaderTester.h
js/src/frontend/BytecodeCompiler.h
js/src/frontend/FullParseHandler.h
js/src/frontend/ParseContext-inl.h
js/src/frontend/ParseContext.h
js/src/frontend/ParseNode.h
js/src/frontend/Parser.cpp
js/src/frontend/Parser.h
js/src/frontend/SharedContext.h
js/src/jsapi-tests/testBinASTReader.cpp
js/src/moz.build
new file mode 100644
--- /dev/null
+++ b/js/src/frontend/BinSource.cpp
@@ -0,0 +1,2504 @@
+#include "frontend/BinSource.h"
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Casting.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Move.h"
+#include "mozilla/PodOperations.h"
+#include "mozilla/Vector.h"
+
+#include "frontend/BinTokenReaderTester.h"
+#include "frontend/FullParseHandler.h"
+#include "frontend/Parser.h"
+#include "frontend/SharedContext.h"
+
+#include "vm/RegExpObject.h"
+
+#include "frontend/ParseContext-inl.h"
+#include "frontend/ParseNode-inl.h"
+
+
+// # About compliance with EcmaScript
+//
+// For the moment, this parser implements ES5. Future versions will be extended
+// to ES6 and further on.
+//
+// By design, it does NOT implement Annex B.3.3. If possible, we would like
+// to avoid going down that rabbit hole.
+//
+//
+// # About the AST
+//
+// At this stage of experimentation, the AST specifications change often. This
+// version of the parser attempts to implement
+// https://gist.github.com/Yoric/2390f0367515c079172be2526349b294
+//
+//
+// # About validating the AST
+//
+// Normally, this implementation validates all properties of the AST *except* the
+// order of fields, which is partially constrained by the AST spec (e.g. in a block,
+// field `scope` must appear before field `body`, etc.).
+//
+//
+// # About names and scopes
+//
+// One of the key objectives of the BinAST syntax is to be able to entirely skip
+// parsing inner functions until they are needed. With a purely syntactic AST,
+// this is generally impossible, as we would need to walk the AST to find
+// lexically-bound/var-bound variables, instances of direct eval, etc.
+//
+// To achieve this, BinAST files contain scope data, as instances of
+// `BinJS:Scope` nodes. Rather than walking the AST to assign bindings
+// to scopes, we extract data from the `BinJS:Scope` and check it lazily,
+// once we actually need to walk the AST.
+//
+// WARNING: The current implementation DOES NOT perform the check yet. It
+// is therefore unsafe.
+//
+// # About directives
+//
+// Currently, directives are ignored and treated as regular strings.
+//
+// They should be treated lazily (whenever we open a subscope), like bindings.
+
+// Evaluate an expression, checking that the result is not 0.
+//
+// Throw `cx->alreadyReportedError()` if it returns 0/nullptr.
+#define TRY(EXPR) \
+    do { \
+        if (!EXPR) \
+            return cx_->alreadyReportedError(); \
+    } while(false)
+
+
+#define TRY_VAR(VAR, EXPR) \
+    do { \
+        VAR = EXPR; \
+        if (!VAR) \
+            return cx_->alreadyReportedError(); \
+    } while (false)
+
+#define TRY_DECL(VAR, EXPR) \
+    auto VAR = EXPR; \
+    if (!VAR) \
+       return cx_->alreadyReportedError();
+
+#define TRY_EMPL(VAR, EXPR) \
+    do { \
+        auto _tryEmplResult = EXPR; \
+        if (!_tryEmplResult) \
+            return cx_->alreadyReportedError(); \
+        VAR.emplace(_tryEmplResult.unwrap()); \
+    } while (false)
+
+#define MOZ_TRY_EMPLACE(VAR, EXPR) \
+    do { \
+        auto _tryEmplResult = EXPR; \
+        if (_tryEmplResult.isErr()) \
+            return ::mozilla::Err(_tryEmplResult.unwrapErr()); \
+        VAR.emplace(_tryEmplResult.unwrap()); \
+    } while (false)
+
+using namespace mozilla;
+
+namespace js {
+namespace frontend {
+
+using AutoList = BinTokenReaderTester::AutoList;
+using AutoTaggedTuple = BinTokenReaderTester::AutoTaggedTuple;
+using AutoTuple = BinTokenReaderTester::AutoTuple;
+using BinFields = BinTokenReaderTester::BinFields;
+using Chars = BinTokenReaderTester::Chars;
+using NameBag = GCHashSet<JSString*>;
+using Names = GCVector<JSString*, 8>;
+using UsedNamePtr = UsedNameTracker::UsedNameMap::Ptr;
+
+namespace {
+    // Compare a bunch of `uint8_t` values (as returned by the tokenizer_) with
+    // a string literal (and ONLY a string literal).
+    template<size_t N>
+    bool operator==(const Chars& left, const char (&right)[N]) {
+        return BinTokenReaderTester::equals(left, right);
+    }
+
+    bool isMethod(BinKind kind) {
+        switch (kind) {
+          case BinKind::ObjectMethod:
+          case BinKind::ObjectGetter:
+          case BinKind::ObjectSetter:
+            return true;
+          default:
+            return false;
+        }
+    }
+
+#if defined(DEBUG)
+    bool isMethodOrFunction(BinKind kind) {
+        if (isMethod(kind))
+            return true;
+        if (kind == BinKind::FunctionExpression || kind == BinKind::FunctionDeclaration)
+            return true;
+        return false;
+    }
+#endif // defined(DEBUG)
+}
+
+JS::Result<ParseNode*>
+BinASTParser::parse(const Vector<uint8_t>& data)
+{
+    return parse(data.begin(), data.length());
+}
+
+JS::Result<ParseNode*>
+BinASTParser::parse(const uint8_t* start, const size_t length)
+{
+    auto result = parseAux(start, length);
+    poison(); // Make sure that the parser is never used again accidentally.
+    return result;
+}
+
+
+JS::Result<ParseNode*>
+BinASTParser::parseAux(const uint8_t* start, const size_t length)
+{
+    tokenizer_.emplace(cx_, start, length);
+
+    Directives directives(options().strictOption);
+    GlobalSharedContext globalsc(cx_, ScopeKind::Global,
+                                 directives, options().extraWarningsOption);
+    BinParseContext globalpc(cx_, this, &globalsc, /* newDirectives = */ nullptr);
+    if (!globalpc.init())
+        return cx_->alreadyReportedError();
+
+    ParseContext::VarScope varScope(cx_, &globalpc, usedNames_);
+    if (!varScope.init(&globalpc))
+        return cx_->alreadyReportedError();
+
+    ParseNode* result(nullptr);
+    MOZ_TRY_VAR(result, parseProgram());
+
+    Maybe<GlobalScope::Data*> bindings = NewGlobalScopeData(cx_, varScope, alloc_, parseContext_);
+    if (!bindings)
+        return cx_->alreadyReportedError();
+    globalsc.bindings = *bindings;
+
+    return result; // Magic conversion to Ok.
+}
+
+Result<ParseNode*>
+BinASTParser::parseProgram()
+{
+    BinKind kind;
+    BinFields fields(cx_);
+    AutoTaggedTuple guard(*tokenizer_);
+
+    TRY(tokenizer_->enterTaggedTuple(kind, fields, guard));
+    if (kind != BinKind::Program)
+        return this->raiseInvalidKind("Program", kind);
+
+    ParseNode* result;
+    MOZ_TRY_VAR(result, parseBlockStatementAux(kind, fields));
+
+    TRY(guard.done());
+    return result;
+}
+
+JS::Result<ParseNode*>
+BinASTParser::parseBlockStatement()
+{
+    BinKind kind;
+    BinFields fields(cx_);
+    AutoTaggedTuple guard(*tokenizer_);
+
+    TRY(tokenizer_->enterTaggedTuple(kind, fields, guard));
+    ParseNode* result(nullptr);
+    switch (kind) {
+      default:
+        return raiseInvalidKind("BlockStatement", kind);
+      case BinKind::BlockStatement:
+        MOZ_TRY_VAR(result, parseBlockStatementAux(kind, fields));
+        break;
+    }
+
+    TRY(guard.done());
+    return result;
+}
+
+JS::Result<Ok>
+BinASTParser::parseAndUpdateScopeNames(ParseContext::Scope& scope, DeclarationKind kind)
+{
+    AutoList guard(*tokenizer_);
+    uint32_t length = 0;
+
+    TRY(tokenizer_->enterList(length, guard));
+    RootedAtom name(cx_);
+    for (uint32_t i = 0; i < length; ++i) {
+        name = nullptr;
+
+        MOZ_TRY(readString(&name));
+        auto ptr = scope.lookupDeclaredNameForAdd(name);
+        if (ptr) {
+            if (kind == DeclarationKind::Let || kind == DeclarationKind::Const)
+                return raiseError("Variable redeclaration");
+
+#if defined(DEBUG)
+            // FIXME: Fix binjs-ref.
+            fprintf(stderr, "Weird: `var` redeclaration. Check encoder: ");
+            name->dump();
+            fprintf(stderr, "\n");
+#endif // defined(DEBUG)
+            continue;
+        }
+
+        TRY(scope.addDeclaredName(parseContext_, ptr, name.get(), kind, tokenizer_->offset()));
+    }
+    TRY(guard.done());
+    return Ok();
+}
+
+JS::Result<Ok>
+BinASTParser::parseAndUpdateCurrentScope()
+{
+    return parseAndUpdateScope(parseContext_->varScope(), *parseContext_->innermostScope());
+}
+
+JS::Result<Ok>
+BinASTParser::parseAndUpdateScope(ParseContext::Scope& varScope, ParseContext::Scope& letScope)
+{
+    BinKind kind;
+    BinFields fields(cx_);
+    AutoTaggedTuple guard(*tokenizer_);
+
+    TRY(tokenizer_->enterTaggedTuple(kind, fields, guard));
+    switch (kind) {
+      default:
+        return raiseInvalidKind("Scope", kind);
+      case BinKind::BINJS_Scope:
+        for (auto field : fields) {
+            switch (field) {
+              case BinField::BINJS_HasDirectEval:
+                MOZ_TRY(readBool()); // Currently ignored.
+                break;
+              case BinField::BINJS_LetDeclaredNames:
+                MOZ_TRY(parseAndUpdateScopeNames(letScope, DeclarationKind::Let));
+                break;
+              case BinField::BINJS_ConstDeclaredNames:
+                MOZ_TRY(parseAndUpdateScopeNames(letScope, DeclarationKind::Const));
+                break;
+              case BinField::BINJS_VarDeclaredNames:
+                MOZ_TRY(parseAndUpdateScopeNames(varScope, DeclarationKind::Var));
+                break;
+              case BinField::BINJS_CapturedNames: {
+                Rooted<Maybe<Names>> names(cx_); //FIXME: Currently ignored.
+                MOZ_TRY(parseStringList(&names));
+                break;
+              }
+              default:
+                return raiseInvalidField("Scope", field);
+            }
+        }
+        break;
+    }
+
+    TRY(guard.done());
+    return Ok();
+}
+
+JS::Result<ParseNode*>
+BinASTParser::parseBlockStatementAux(const BinKind kind, const BinFields& fields)
+{
+    ParseContext::Statement stmt(parseContext_, StatementKind::Block);
+    ParseContext::Scope scope(cx_, parseContext_, usedNames_);
+    TRY(scope.init(parseContext_));
+
+    ParseNode* body(nullptr);
+    ParseNode* directives(nullptr); // FIXME: Largely ignored
+
+    for (auto field : fields) {
+        switch (field) {
+          case BinField::BINJS_Scope:
+            MOZ_TRY(parseAndUpdateCurrentScope());
+            break;
+          case BinField::Body:
+            MOZ_TRY_VAR(body, parseStatementList());
+            break;
+          case BinField::Directives:
+            if (kind != BinKind::Program)
+                return raiseInvalidField("BlockStatement", field);
+            MOZ_TRY_VAR(directives, parseDirectiveList());
+            break;
+          default:
+            return raiseInvalidField("BlockStatement", field);
+        }
+    }
+
+    // In case of absent optional fields, inject default values.
+    if (!body)
+        body = factory_.newStatementList(tokenizer_->pos());
+
+    MOZ_TRY_VAR(body, appendDirectivesToBody(body, directives));
+
+    ParseNode* result;
+    if (kind == BinKind::Program) {
+        result = body;
+    } else {
+        TRY_DECL(bindings, NewLexicalScopeData(cx_, scope, alloc_, parseContext_));
+        TRY_VAR(result, factory_.newLexicalScope(*bindings, body));
+    }
+
+    return result;
+}
+
+JS::Result<ParseNode*>
+BinASTParser::appendDirectivesToBody(ParseNode* body, ParseNode* directives)
+{
+    ParseNode* result = body;
+    if (directives && directives->pn_count >= 1) {
+        MOZ_ASSERT(directives->isArity(PN_LIST));
+
+        // Convert directive list to a list of strings.
+        TRY_DECL(prefix, factory_.newStatementList(directives->pn_head->pn_pos));
+        for (ParseNode* iter = directives->pn_head; iter != nullptr; iter = iter->pn_next) {
+            TRY_DECL(statement, factory_.newExprStatement(iter, iter->pn_pos.end));
+            prefix->appendWithoutOrderAssumption(statement);
+        }
+
+        // Prepend to the body.
+        ParseNode* iter = body->pn_head;
+        while (iter) {
+            ParseNode* next = iter->pn_next;
+            prefix->appendWithoutOrderAssumption(iter);
+            iter = next;
+        }
+        prefix->setKind(body->getKind());
+        prefix->setOp(body->getOp());
+        result = prefix;
+#if defined(DEBUG)
+        result->checkListConsistency();
+#endif // defined(DEBUG)
+    }
+
+    return result;
+}
+
+JS::Result<Ok>
+BinASTParser::parseStringList(MutableHandle<Maybe<Names>> out)
+{
+    MOZ_ASSERT(out.get().isNothing()); // Sanity check: the node must not have been parsed yet.
+
+    uint32_t length;
+    AutoList guard(*tokenizer_);
+
+    Names result(cx_);
+
+    TRY(tokenizer_->enterList(length, guard));
+    if (!result.reserve(length))
+        return raiseOOM();
+
+    RootedAtom string(cx_);
+    for (uint32_t i = 0; i < length; ++i) {
+        string = nullptr;
+
+        MOZ_TRY(readString(&string));
+        result.infallibleAppend(Move(string)); // Checked in the call to `reserve`.
+    }
+
+    TRY(guard.done());
+    out.set(Move(Some(Move(result))));
+    return Ok();
+}
+
+JS::Result<ParseNode*>
+BinASTParser::parseStatementList()
+{
+    uint32_t length;
+    AutoList guard(*tokenizer_);
+
+    TRY_DECL(result, factory_.newStatementList(tokenizer_->pos()));
+
+    TRY(tokenizer_->enterList(length, guard));
+    for (uint32_t i = 0; i < length; ++i) {
+        BinKind kind;
+        BinFields fields(cx_);
+        AutoTaggedTuple guard(*tokenizer_);
+
+        ParseNode* statement(nullptr);
+
+        TRY(tokenizer_->enterTaggedTuple(kind, fields, guard));
+        switch (kind) {
+          case BinKind::FunctionDeclaration:
+            MOZ_TRY_VAR(statement, parseFunctionAux(kind, fields));
+            break;
+          default:
+            MOZ_TRY_VAR(statement, parseStatementAux(kind, fields));
+            break;
+        }
+
+        TRY(guard.done());
+        result->appendWithoutOrderAssumption(statement);
+    }
+
+    TRY(guard.done());
+    return result;
+}
+
+JS::Result<ParseNode*>
+BinASTParser::parseStatement()
+{
+    BinKind kind;
+    BinFields fields(cx_);
+    AutoTaggedTuple guard(*tokenizer_);
+
+    TRY(tokenizer_->enterTaggedTuple(kind, fields, guard));
+    ParseNode* result;
+    MOZ_TRY_VAR(result, parseStatementAux(kind, fields));
+
+    TRY(guard.done());
+    return result;
+}
+
+JS::Result<ParseNode*>
+BinASTParser::parseStatementAux(const BinKind kind, const BinFields& fields)
+{
+    const size_t start = tokenizer_->offset();
+
+    ParseNode* result(nullptr);
+    switch (kind) {
+      case BinKind::EmptyStatement: {
+        TRY_VAR(result, factory_.newEmptyStatement(tokenizer_->pos(start)));
+        break;
+      }
+      case BinKind::BlockStatement:
+        MOZ_TRY_VAR(result, parseBlockStatementAux(kind, fields));
+        break;
+      case BinKind::ExpressionStatement:
+        MOZ_TRY_VAR(result, parseExpressionStatementAux(kind, fields));
+        break;
+      case BinKind::DebuggerStatement: {
+        TRY_VAR(result, factory_.newDebuggerStatement(tokenizer_->pos(start)));
+        break;
+      }
+      case BinKind::WithStatement: {
+        ParseNode* body(nullptr);
+        ParseNode* expr(nullptr);
+        for (auto field : fields) {
+            switch (field) {
+              case BinField::Body:
+                MOZ_TRY_VAR(body, parseStatement());
+                break;
+              case BinField::Object:
+                MOZ_TRY_VAR(expr, parseExpression());
+                break;
+              default:
+                return raiseInvalidField("WithStatement", field);
+            }
+        }
+
+        if (!body)
+            return raiseMissingField("WithStatement", BinField::Body);
+        if (!expr)
+            return raiseMissingField("WithStatement", BinField::Object);
+
+        TRY_VAR(result, factory_.newWithStatement(start, expr, body));
+
+        break;
+      }
+      case BinKind::ReturnStatement: {
+        if (!parseContext_->isFunctionBox()) {
+            // Return statements are permitted only inside functions.
+            return raiseInvalidKind("Toplevel Statement", kind);
+        }
+        parseContext_->functionBox()->usesReturn = true;
+
+        ParseNode* arg(nullptr);
+        for (auto field : fields) {
+            switch (field) {
+              case BinField::Argument:
+                MOZ_TRY_VAR(arg, parseExpression());
+                break;
+              default:
+                return raiseInvalidField("ReturnStatement", field);
+            }
+        }
+
+        TRY_VAR(result, factory_.newReturnStatement(arg, tokenizer_->pos(start)));
+
+        break;
+      }
+      case BinKind::LabeledStatement: {
+        // We check for the existence of the jump target when parsing `break label;` or `continue label;`.
+        ParseContext::Statement stmt(parseContext_, StatementKind::Label);
+        ParseNode* label(nullptr);
+        ParseNode* body(nullptr);
+
+        for (auto field : fields) {
+            switch (field) {
+              case BinField::Label:
+                MOZ_TRY_VAR(label, parseIdentifier());
+                break;
+              case BinField::Body: {
+                if (!label)  // By order of fields, `label` MUST always be before `body` in the file.
+                    return raiseMissingField("LabeledStatement", BinField::Label);
+                MOZ_ASSERT(label->name());
+                ParseContext::LabelStatement stmt(parseContext_, label->name());
+                MOZ_TRY_VAR(body, parseStatement());
+                break;
+              }
+              default:
+                return raiseInvalidField("LabeledStatement", field);
+            }
+        }
+
+        if (!label)
+            return raiseMissingField("LabeledStatement", BinField::Label);
+        if (!body)
+            return raiseMissingField("LabeledStatement", BinField::Body);
+
+        TRY_VAR(result, factory_.newLabeledStatement(label->name(), body, start));
+
+        break;
+      }
+
+      case BinKind::BreakStatement:
+      case BinKind::ContinueStatement:
+        MOZ_TRY_VAR(result, parseBreakOrContinueStatementAux(kind, fields));
+        break;
+
+      case BinKind::IfStatement: {
+        ParseContext::Statement stmt(parseContext_, StatementKind::If);
+
+        ParseNode* test(nullptr);
+        ParseNode* consequent(nullptr);
+        ParseNode* alternate(nullptr); // Optional
+
+        for (auto field : fields) {
+            switch (field) {
+              case BinField::Test:
+                MOZ_TRY_VAR(test, parseExpression());
+                break;
+              case BinField::Consequent:
+                MOZ_TRY_VAR(consequent, parseStatement());
+                break;
+              case BinField::Alternate:
+                MOZ_TRY_VAR(alternate, parseStatement());
+                break;
+              default:
+                return raiseInvalidField("IfStatement", field);
+            }
+        }
+
+        if (!test)
+            return raiseMissingField("IfStatement", BinField::Test);
+        if (!consequent)
+            return raiseMissingField("IfStatement", BinField::Consequent);
+
+        TRY_VAR(result, factory_.newIfStatement(start, test, consequent, alternate));
+
+        break;
+      }
+      case BinKind::SwitchStatement: {
+        ParseContext::Statement stmt(parseContext_, StatementKind::Switch);
+        ParseNode* discriminant(nullptr);
+        ParseNode* cases(nullptr);
+
+        for (auto field : fields) {
+            switch (field) {
+              case BinField::Discriminant: {
+                MOZ_TRY_VAR(discriminant, parseExpression());
+                break;
+              }
+              case BinField::Cases: {
+                MOZ_TRY_VAR(cases, parseSwitchCaseList());
+                break;
+              }
+              default:
+                return raiseInvalidField("SwitchStatement", field);
+            }
+        }
+
+        if (!discriminant)
+            return raiseMissingField("SwitchStatement", BinField::Discriminant);
+        if (!cases) {
+            TRY_VAR(cases, factory_.newStatementList(tokenizer_->pos()));
+
+            TRY_VAR(cases, factory_.newLexicalScope(nullptr, cases));
+        }
+
+        TRY_VAR(result, factory_.newSwitchStatement(start, discriminant, cases));
+
+        break;
+      }
+
+      case BinKind::ThrowStatement: {
+        ParseNode* arg(nullptr);
+        for (auto field : fields) {
+            if (field != BinField::Argument)
+                return raiseInvalidField("ThrowStatement", field);
+
+            MOZ_TRY_VAR(arg, parseExpression());
+        }
+
+        if (!arg)
+            return raiseMissingField("ThrowStatement", BinField::Argument);
+
+        TRY_VAR(result, factory_.newThrowStatement(arg, tokenizer_->pos(start)));
+
+        break;
+      }
+
+      case BinKind::TryStatement: {
+        ParseNode* block(nullptr);
+        ParseNode* handler(nullptr);
+        ParseNode* finalizer(nullptr);
+
+        for (auto field : fields) {
+            switch (field) {
+              case BinField::Block: {
+                ParseContext::Statement stmt(parseContext_, StatementKind::Try);
+                ParseContext::Scope scope(cx_, parseContext_, usedNames_);
+                TRY(scope.init(parseContext_));
+                MOZ_TRY_VAR(block, parseBlockStatement());
+                break;
+              }
+              case BinField::Handler:
+                MOZ_TRY_VAR(handler, parseCatchClause());
+                break;
+
+              case BinField::Finalizer: {
+                ParseContext::Statement stmt(parseContext_, StatementKind::Finally);
+                ParseContext::Scope scope(cx_, parseContext_, usedNames_);
+                TRY(scope.init(parseContext_));
+                MOZ_TRY_VAR(finalizer, parseBlockStatement());
+                break;
+              }
+
+              default:
+                return raiseInvalidField("TryStatement", field);
+            }
+        }
+
+        if (!block)
+            return raiseMissingField("TryStatement", BinField::Handler);
+        if (!handler && !finalizer)
+            return raiseMissingField("TryStatement (without catch)", BinField::Finalizer);
+
+        TRY_VAR(result, factory_.newTryStatement(start, block, handler, finalizer));
+        break;
+      }
+
+      case BinKind::WhileStatement:
+      case BinKind::DoWhileStatement: {
+        ParseContext::Statement stmt(parseContext_, kind == BinKind::WhileStatement ? StatementKind::WhileLoop : StatementKind::DoLoop);
+        ParseNode* test(nullptr);
+        ParseNode* body(nullptr);
+
+        for (auto field : fields) {
+            switch (field) {
+              case BinField::Test:
+                MOZ_TRY_VAR(test, parseExpression());
+                break;
+              case BinField::Body:
+                MOZ_TRY_VAR(body, parseStatement());
+                break;
+              default:
+                return raiseInvalidField("WhileStatement | DoWhileStatement", field);
+            }
+        }
+
+        if (!test)
+            return raiseMissingField("WhileStatement | DoWhileStatement", BinField::Test);
+        if (!body)
+            return raiseMissingField("WhileStatement | DoWhileStatement", BinField::Body);
+
+        if (kind == BinKind::WhileStatement)
+            TRY_VAR(result, factory_.newWhileStatement(start, test, body));
+        else
+            TRY_VAR(result, factory_.newDoWhileStatement(body, test, tokenizer_->pos(start)));
+
+        break;
+      }
+      case BinKind::ForStatement: {
+        ParseContext::Statement stmt(parseContext_, StatementKind::ForLoop);
+
+        // Implicit scope around the `for`, used to store `for (let x; ...; ...)`
+        // or `for (const x; ...; ...)`-style declarations. Detail on the
+        // declaration is stored as part of `BINJS_Scope`.
+        ParseContext::Scope scope(cx_, parseContext_, usedNames_);
+        TRY(scope.init(parseContext_));
+        ParseNode* init(nullptr); // Optional
+        ParseNode* test(nullptr); // Optional
+        ParseNode* update(nullptr); // Optional
+        ParseNode* body(nullptr); // Required
+
+        for (auto field : fields) {
+            switch (field) {
+              case BinField::Init:
+                MOZ_TRY_VAR(init, parseForInit());
+                break;
+              case BinField::Test:
+                MOZ_TRY_VAR(test, parseExpression());
+                break;
+              case BinField::Update:
+                MOZ_TRY_VAR(update, parseExpression());
+                break;
+              case BinField::BINJS_Scope: // The scope always appears before the body.
+                MOZ_TRY(parseAndUpdateCurrentScope());
+                break;
+              case BinField::Body:
+                MOZ_TRY_VAR(body, parseStatement());
+                break;
+              default:
+                return raiseInvalidField("ForStatement", field);
+            }
+        }
+
+        if (!body)
+            return raiseMissingField("ForStatement", BinField::Body);
+
+        TRY_DECL(forHead, factory_.newForHead(init, test, update, tokenizer_->pos(start)));
+        TRY_VAR(result, factory_.newForStatement(start, forHead, body, /* iflags = */ 0));
+
+        if (!scope.isEmpty()) {
+            TRY_DECL(bindings, NewLexicalScopeData(cx_, scope, alloc_, parseContext_));
+            TRY_VAR(result, factory_.newLexicalScope(*bindings, result));
+        }
+
+        break;
+      }
+      case BinKind::ForInStatement: {
+        ParseContext::Statement stmt(parseContext_, StatementKind::ForInLoop);
+
+        // Implicit scope around the `for`, used to store `for (let x in  ...)`
+        // or `for (const x in ...)`-style declarations. Detail on the
+        // declaration is stored as part of `BINJS_Scope`.
+        ParseContext::Scope scope(cx_, parseContext_, usedNames_);
+        TRY(scope.init(parseContext_));
+        ParseNode* left(nullptr);
+        ParseNode* right(nullptr);
+        ParseNode* body(nullptr);
+
+        for (auto field : fields) {
+            switch (field) {
+              case BinField::Left:
+                MOZ_TRY_VAR(left, parseForInInit());
+                break;
+              case BinField::Right:
+                MOZ_TRY_VAR(right, parseExpression());
+                break;
+              case BinField::Body:
+                MOZ_TRY_VAR(body, parseStatement());
+                break;
+              case BinField::BINJS_Scope:
+                MOZ_TRY(parseAndUpdateCurrentScope());
+                break;
+              default:
+                return raiseInvalidField("ForInStatement", field);
+            }
+        }
+
+        if (!left)
+            return raiseMissingField("ForInStatement", BinField::Left);
+        if (!right)
+            return raiseMissingField("ForInStatement", BinField::Right);
+        if (!body)
+            return raiseMissingField("ForInStatement", BinField::Body);
+
+        TRY_DECL(forHead, factory_.newForInOrOfHead(PNK_FORIN, left, right, tokenizer_->pos(start)));
+        TRY_VAR(result, factory_.newForStatement(start, forHead, body, /*flags*/ 0));
+
+        if (!scope.isEmpty()) {
+            TRY_DECL(bindings, NewLexicalScopeData(cx_, scope, alloc_, parseContext_));
+            TRY_VAR(result, factory_.newLexicalScope(*bindings, result));
+        }
+        break;
+      }
+
+      case BinKind::VariableDeclaration:
+        MOZ_TRY_VAR(result, parseVariableDeclarationAux(kind, fields));
+        break;
+
+      default:
+        return raiseInvalidKind("Statement", kind);
+    }
+
+    return result;
+}
+
+JS::Result<ParseNode*>
+BinASTParser::parseBreakOrContinueStatementAux(const BinKind kind, const BinFields& fields)
+{
+    const auto start = tokenizer_->offset();
+    ParseNode* label(nullptr);
+
+    for (auto field : fields) {
+        switch (field) {
+          case BinField::Label:
+            MOZ_TRY_VAR(label, parsePattern());
+
+            if (label && !label->isKind(PNK_NAME))
+                return raiseError("ContinueStatement | BreakStatement - Label MUST be an identifier"); // FIXME: This should be changed in the grammar.
+
+            break;
+          default:
+            return raiseInvalidField("ContinueStatement", field);
+        }
+    }
+
+    TokenPos pos = tokenizer_->pos(start);
+    ParseNode* result;
+    if (kind == BinKind::ContinueStatement) {
+#if 0 // FIXME: We probably need to fix the AST before making this check.
+        auto validity = parseContext_->checkContinueStatement(label ? label->name() : nullptr);
+        if (validity.isErr()) {
+            switch (validity.unwrapErr()) {
+              case ParseContext::ContinueStatementError::NotInALoop:
+                return raiseError(kind, "Not in a loop");
+              case ParseContext::ContinueStatementError::LabelNotFound:
+                return raiseError(kind, "Label not found");
+            }
+        }
+#endif // 0
+        // Ok, this is a valid continue statement.
+        TRY_VAR(result, factory_.newContinueStatement(label ? label->name() : nullptr, pos));
+    } else {
+#if 0 // FIXME: We probably need to fix the AST before making this check.
+        auto validity = parseContext_->checkBreakStatement(label ? label->name() : nullptr);
+        if (validity.isErr()) {
+            switch (validity.unwrapErr()) {
+              case ParseContext::BreakStatementError::ToughBreak:
+                 return raiseError(kind, "Not in a loop");
+              case ParseContext::BreakStatementError::LabelNotFound:
+                 return raiseError(kind, "Label not found");
+            }
+        }
+#endif // 0
+        // Ok, this is a valid break statement.
+        TRY_VAR(result, factory_.newBreakStatement(label ? label->name() : nullptr, pos));
+    }
+
+    MOZ_ASSERT(result);
+
+    return result;
+}
+
+JS::Result<ParseNode*>
+BinASTParser::parseForInit()
+{
+    // This can be either a VarDecl or an Expression.
+    BinFields fields(cx_);
+    AutoTaggedTuple guard(*tokenizer_);
+    BinKind kind;
+
+    TRY(tokenizer_->enterTaggedTuple(kind, fields, guard));
+    ParseNode* result(nullptr);
+
+    switch (kind) {
+      case BinKind::VariableDeclaration:
+        MOZ_TRY_VAR(result, parseVariableDeclarationAux(kind, fields));
+        break;
+      default:
+        // Parse as expression
+        MOZ_TRY_VAR(result, parseExpressionAux(kind, fields));
+        break;
+    }
+
+    TRY(guard.done());
+    return result;
+}
+
+JS::Result<ParseNode*>
+BinASTParser::parseForInInit()
+{
+    // This can be either a VarDecl or a Pattern.
+    BinFields fields(cx_);
+    AutoTaggedTuple guard(*tokenizer_);
+    BinKind kind;
+
+    TRY(tokenizer_->enterTaggedTuple(kind, fields, guard));
+    ParseNode* result(nullptr);
+
+    switch (kind) {
+      case BinKind::VariableDeclaration:
+        MOZ_TRY_VAR(result, parseVariableDeclarationAux(kind, fields));
+        break;
+      default:
+        // Parse as expression. Not a joke: http://www.ecma-international.org/ecma-262/5.1/index.html#sec-12.6.4 .
+        MOZ_TRY_VAR(result, parseExpressionAux(kind, fields));
+        break;
+    }
+
+    TRY(guard.done());
+    return result;
+}
+
+JS::Result<ParseNode*>
+BinASTParser::parseFunctionAux(const BinKind kind, const BinFields& fields)
+{
+    MOZ_ASSERT(isMethodOrFunction(kind));
+
+    const size_t start = tokenizer_->offset();
+
+    ParseNode* id(nullptr);
+    ParseNode* params(nullptr);
+    ParseNode* body(nullptr);
+    ParseNode* directives(nullptr); // Largely ignored for the moment.
+    ParseNode* key(nullptr);  // Methods only
+
+    // Allocate the function before walking down the tree.
+    RootedFunction fun(cx_);
+    TRY_VAR(fun, NewFunctionWithProto(cx_,
+            /*native*/nullptr,
+            /*nargs ?*/0,
+            /*flags */ JSFunction::INTERPRETED_NORMAL,
+            /*enclosing env*/nullptr,
+            /*name*/ nullptr, // Will be known later
+            /*proto*/ nullptr,
+            /*alloc*/gc::AllocKind::FUNCTION,
+            TenuredObject
+    ));
+    TRY_DECL(funbox, alloc_.new_<FunctionBox>(cx_,
+        traceListHead_,
+        fun,
+        /* toStringStart = */0,
+        /* directives = */Directives(parseContext_),
+        /* extraWarning = */false,
+        GeneratorKind::NotGenerator,
+        FunctionAsyncKind::SyncFunction
+    ));
+
+    traceListHead_ = funbox;
+
+    FunctionSyntaxKind syntax;
+    switch (kind) {
+      case BinKind::FunctionDeclaration:
+        syntax = Statement;
+        break;
+      case BinKind::FunctionExpression:
+        syntax = PrimaryExpression; // FIXME: Probably doesn't work.
+        break;
+      case BinKind::ObjectMethod:
+        syntax = Method;
+        break;
+      case BinKind::ObjectGetter:
+        syntax = Getter;
+        break;
+      case BinKind::ObjectSetter:
+        syntax = Setter;
+        break;
+      default:
+        MOZ_CRASH("Invalid FunctionSyntaxKind"); // Checked above.
+    }
+    funbox->initWithEnclosingParseContext(parseContext_, syntax);
+
+    // Container scopes.
+    ParseContext::Scope& varScope = parseContext_->varScope();
+    ParseContext::Scope* letScope = parseContext_->innermostScope();
+
+    // Push a new ParseContext.
+    BinParseContext funpc(cx_, this, funbox, /* newDirectives = */ nullptr);
+    TRY(funpc.init());
+    parseContext_->functionScope().useAsVarScope(parseContext_);
+    MOZ_ASSERT(parseContext_->isFunctionBox());
+
+    for (auto field : fields) {
+        switch (field) {
+          case BinField::Id:
+            MOZ_TRY_VAR(id, parseIdentifier());
+            break;
+          case BinField::Params:
+            MOZ_TRY_VAR(params, parseArgumentList());
+            break;
+          case BinField::BINJS_Scope:
+            // This scope information affects the scopes contained in the function body. MUST appear before the `body`.
+            MOZ_TRY(parseAndUpdateScope(varScope, *letScope));
+            break;
+          case BinField::Directives:
+            MOZ_TRY_VAR(directives, parseDirectiveList());
+            break;
+          case BinField::Body:
+            MOZ_TRY_VAR(body, parseBlockStatement());
+            break;
+          case BinField::Key:
+            if (!isMethod(kind))
+                return raiseInvalidField("Functions (unless defined as methods)", field);
+
+            MOZ_TRY_VAR(key, parseObjectPropertyName());
+            break;
+          default:
+            return raiseInvalidField("Function", field);
+        }
+    }
+
+    // Inject default values for absent fields.
+    if (!params)
+        TRY_VAR(params, new_<ListNode>(PNK_PARAMSBODY, tokenizer_->pos()));
+
+    if (!body)
+        TRY_VAR(body, factory_.newStatementList(tokenizer_->pos()));
+
+    if (kind == BinKind::FunctionDeclaration && !id) {
+        // The name is compulsory only for function declarations.
+        return raiseMissingField("FunctionDeclaration", BinField::Id);
+    }
+
+    // Reject if required values are missing.
+    if (isMethod(kind) && !key)
+        return raiseMissingField("method", BinField::Key);
+
+    if (id)
+        fun->initAtom(id->pn_atom);
+
+    MOZ_ASSERT(params->isArity(PN_LIST));
+
+    if (!(body->isKind(PNK_LEXICALSCOPE) && body->pn_u.scope.body->isKind(PNK_STATEMENTLIST))) {
+        // Promote to lexical scope + statement list.
+        if (!body->isKind(PNK_STATEMENTLIST)) {
+            TRY_DECL(list, factory_.newStatementList(tokenizer_->pos(start)));
+
+            list->initList(body);
+            body = list;
+        }
+
+        // Promote to lexical scope.
+        TRY_VAR(body, factory_.newLexicalScope(nullptr, body));
+    }
+    MOZ_ASSERT(body->isKind(PNK_LEXICALSCOPE));
+
+    MOZ_TRY_VAR(body, appendDirectivesToBody(body, directives));
+
+    params->appendWithoutOrderAssumption(body);
+
+    TokenPos pos = tokenizer_->pos(start);
+    TRY_DECL(function, kind == BinKind::FunctionDeclaration
+                       ? factory_.newFunctionStatement(pos)
+                       : factory_.newFunctionExpression(pos));
+
+    factory_.setFunctionBox(function, funbox);
+    factory_.setFunctionFormalParametersAndBody(function, params);
+
+    ParseNode* result;
+    if (kind == BinKind::ObjectMethod)
+        TRY_VAR(result, factory_.newObjectMethodOrPropertyDefinition(key, function, AccessorType::None));
+    else if (kind == BinKind::ObjectGetter)
+        TRY_VAR(result, factory_.newObjectMethodOrPropertyDefinition(key, function, AccessorType::Getter));
+    else if (kind == BinKind::ObjectSetter)
+        TRY_VAR(result, factory_.newObjectMethodOrPropertyDefinition(key, function, AccessorType::Setter));
+    else
+        result = function;
+
+    // Now handle bindings.
+    HandlePropertyName dotThis = cx_->names().dotThis;
+    const bool declareThis = hasUsedName(dotThis) || funbox->bindingsAccessedDynamically() || funbox->isDerivedClassConstructor();
+
+    if (declareThis) {
+        ParseContext::Scope& funScope = parseContext_->functionScope();
+        ParseContext::Scope::AddDeclaredNamePtr p = funScope.lookupDeclaredNameForAdd(dotThis);
+        MOZ_ASSERT(!p);
+        TRY(funScope.addDeclaredName(parseContext_, p, dotThis, DeclarationKind::Var,
+                                      DeclaredNameInfo::npos));
+        funbox->setHasThisBinding();
+    }
+
+    TRY_DECL(bindings,
+             NewFunctionScopeData(cx_, parseContext_->functionScope(),
+                                  /* hasParameterExprs = */false, alloc_, parseContext_));
+
+    funbox->functionScopeBindings().set(*bindings);
+    return result;
+}
+
+JS::Result<ParseNode*>
+BinASTParser::parseObjectPropertyName()
+{
+    auto start = tokenizer_->offset();
+
+    BinFields fields(cx_);
+    AutoTaggedTuple guard(*tokenizer_);
+    BinKind kind;
+
+    TRY(tokenizer_->enterTaggedTuple(kind, fields, guard));
+    ParseNode* result;
+    switch (kind) {
+      case BinKind::StringLiteral: {
+        ParseNode* string;
+        MOZ_TRY_VAR(string, parseStringLiteralAux(kind, fields));
+        uint32_t index;
+        if (string->pn_atom->isIndex(&index))
+            TRY_VAR(result, factory_.newNumber(index, NoDecimal, TokenPos(start, tokenizer_->offset())));
+        else
+            result = string;
+
+        break;
+      }
+      case BinKind::NumericLiteral:
+        MOZ_TRY_VAR(result, parseNumericLiteralAux(kind, fields));
+        break;
+      case BinKind::Identifier:
+        MOZ_TRY_VAR(result, parseIdentifierAux(kind, fields, /* expectObjectPropertyName = */ true));
+        break;
+      case BinKind::ComputedPropertyName: {
+        ParseNode* expr;
+        MOZ_TRY_VAR(expr, parseExpressionAux(kind, fields));
+        TRY_VAR(result, factory_.newComputedName(expr, start, tokenizer_->offset()));
+        break;
+      }
+      default:
+        return raiseInvalidKind("ObjectLiteralPropertyName", kind);
+    }
+
+    TRY(guard.done());
+    return result;
+}
+
+JS::Result<ParseNode*>
+BinASTParser::parseVariableDeclarationAux(const BinKind kind, const BinFields& fields)
+{
+    const size_t start = tokenizer_->offset();
+
+    ParseNode* result(nullptr);
+    switch (kind) {
+      default:
+        return raiseInvalidKind("VariableDeclaration", kind);
+      case BinKind::VariableDeclaration:
+        ParseNodeKind pnk = PNK_LIMIT;
+
+        for (auto field : fields) {
+            switch (field) {
+              case BinField::Kind: {
+                Maybe<Chars> kindName;
+                MOZ_TRY(readString(kindName));
+
+                if (*kindName == "let")
+                    pnk = PNK_LET;
+                else if (*kindName == "var")
+                    pnk = PNK_VAR;
+                else if (*kindName == "const")
+                    pnk = PNK_CONST;
+                else
+                    return raiseInvalidEnum("VariableDeclaration", *kindName);
+
+                break;
+              }
+              case BinField::Declarations: {
+                uint32_t length;
+                AutoList guard(*tokenizer_);
+
+                TRY(tokenizer_->enterList(length, guard));
+                TRY_VAR(result, factory_.newDeclarationList(PNK_CONST /*Placeholder*/, tokenizer_->pos(start)));
+
+                for (uint32_t i = 0; i < length; ++i) {
+                    ParseNode* current;
+                    MOZ_TRY_VAR(current, parseVariableDeclarator());
+                    MOZ_ASSERT(current);
+
+                    result->appendWithoutOrderAssumption(current);
+                }
+
+                TRY(guard.done());
+                break;
+              }
+              default:
+                return raiseInvalidField("VariableDeclaration", field);
+            }
+        }
+
+        if (!result || pnk == PNK_LIMIT)
+            return raiseMissingField("VariableDeclaration", BinField::Declarations);
+
+        result->setKind(pnk);
+
+        MOZ_ASSERT(!result->isKind(PNK_NOP));
+    }
+
+    return result;
+}
+
+
+JS::Result<ParseNode*>
+BinASTParser::parseExpressionStatementAux(const BinKind kind, const BinFields& fields)
+{
+    MOZ_ASSERT(kind == BinKind::ExpressionStatement);
+
+    ParseNode* expr(nullptr);
+    for (auto field : fields) {
+        switch (field) {
+          case BinField::Expression:
+            MOZ_TRY_VAR(expr, parseExpression());
+
+            break;
+          default:
+            return raiseInvalidField("ExpressionStatement", field);
+        }
+    }
+
+    if (!expr)
+        return raiseMissingField("ExpressionStatement", BinField::Expression);
+
+    TRY_DECL(result, factory_.newExprStatement(expr, tokenizer_->offset()));
+    return result;
+}
+
+JS::Result<ParseNode*>
+BinASTParser::parseVariableDeclarator()
+{
+    const size_t start = tokenizer_->offset();
+
+    BinKind kind;
+    BinFields fields(cx_);
+    AutoTaggedTuple guard(*tokenizer_);
+
+    TRY(tokenizer_->enterTaggedTuple(kind, fields, guard));
+    if (kind != BinKind::VariableDeclarator)
+        return raiseInvalidKind("VariableDeclarator", kind);
+
+    ParseNode* id(nullptr);
+    ParseNode* init(nullptr); // Optional.
+    for (auto field : fields) {
+        switch (field) {
+          case BinField::Id:
+            MOZ_TRY_VAR(id, parsePattern());
+
+            break;
+          case BinField::Init:
+            MOZ_TRY_VAR(init, parseExpression());
+
+            break;
+          default:
+            return raiseInvalidField("VariableDeclarator", field);
+        }
+    }
+
+    TRY(guard.done());
+    if (!id)
+        return raiseMissingField("VariableDeclarator", BinField::Id);
+
+    ParseNode* result(nullptr);
+
+    // FIXME: Documentation in ParseNode is clearly obsolete.
+    if (id->isKind(PNK_NAME)) {
+        // `var foo [= bar]``
+        TRY_VAR(result, factory_.newName(id->pn_atom->asPropertyName(), tokenizer_->pos(start), cx_));
+
+        if (init)
+            result->pn_expr = init;
+
+    } else {
+        // `var pattern = bar`
+        if (!init) {
+            // Here, `init` is required.
+            return raiseMissingField("VariableDeclarator (with non-trivial pattern)", BinField::Init);
+        }
+
+        TRY_VAR(result, factory_.newAssignment(PNK_ASSIGN, id, init));
+    }
+
+    return result;
+}
+
+JS::Result<ParseNode*>
+BinASTParser::parseExpressionList(bool acceptElisions)
+{
+    const size_t start = tokenizer_->offset();
+
+    uint32_t length;
+    AutoList guard(*tokenizer_);
+
+    TRY(tokenizer_->enterList(length, guard));
+    TRY_DECL(result, factory_.newArrayLiteral(start));
+
+    for (uint32_t i = 0; i < length; ++i) {
+        BinFields fields(cx_);
+        AutoTaggedTuple guard(*tokenizer_);
+        BinKind kind;
+
+        TRY(tokenizer_->enterTaggedTuple(kind, fields, guard));
+        switch (kind) {
+          case BinKind::Elision: {
+            if (!acceptElisions)
+                return raiseInvalidKind("[Expression]", kind);
+
+            MOZ_TRY(parseElisionAux(kind, fields));
+            TRY(!factory_.addElision(result, tokenizer_->pos(start)));
+            break;
+          }
+          default: {
+            ParseNode* expr(nullptr);
+            MOZ_TRY_VAR(expr, parseExpressionAux(kind, fields));
+
+            MOZ_ASSERT(expr);
+            factory_.addArrayElement(result, expr);
+          }
+        }
+
+        TRY(guard.done());
+    }
+
+    TRY(guard.done());
+    return result;
+}
+
+JS::Result<Ok>
+BinASTParser::parseElisionAux(const BinKind kind, const BinFields& fields)
+{
+    MOZ_ASSERT(kind == BinKind::Elision);
+    MOZ_TRY(checkEmptyTuple(kind, fields));
+
+    return Ok();
+}
+
+JS::Result<ParseNode*>
+BinASTParser::parseSwitchCaseList()
+{
+    uint32_t length;
+    AutoList guard(*tokenizer_);
+
+    TRY(tokenizer_->enterList(length, guard));
+    TRY_DECL(list, factory_.newStatementList(tokenizer_->pos()));
+
+    // Set to `true` once we have encountered a `default:` case.
+    // Two `default:` cases is an error.
+    bool haveDefault = false;
+
+    for (uint32_t i = 0; i < length; ++i) {
+        ParseNode* caseNode(nullptr);
+        MOZ_TRY_VAR(caseNode, parseSwitchCase());
+        MOZ_ASSERT(caseNode);
+
+        if (caseNode->pn_left == nullptr) {
+            // Ah, seems that we have encountered a default case.
+            if (haveDefault) {
+                // Oh, wait, two defaults? That's an error.
+                return raiseError("This switch() has more than one `default:` case");
+            }
+            haveDefault = true;
+        }
+        factory_.addCaseStatementToList(list, caseNode);
+    }
+
+    TRY(guard.done());
+    TRY_DECL(result, factory_.newLexicalScope(nullptr, list));
+
+    return result;
+}
+
+JS::Result<ParseNode*>
+BinASTParser::parseExpression()
+{
+    BinFields fields(cx_);
+    AutoTaggedTuple guard(*tokenizer_);
+    BinKind kind;
+
+    TRY(tokenizer_->enterTaggedTuple(kind, fields, guard));
+    ParseNode* result(nullptr);
+    MOZ_TRY_VAR(result, parseExpressionAux(kind, fields));
+
+    TRY(guard.done());
+    return result;
+}
+
+JS::Result<ParseNode*>
+BinASTParser::parseExpressionAux(const BinKind kind, const BinFields& fields)
+{
+    const size_t start = tokenizer_->offset();
+
+    ParseNode* result(nullptr);
+
+    switch (kind) {
+      case BinKind::Identifier: {
+        MOZ_TRY_VAR(result, parseIdentifierAux(kind, fields));
+        break;
+      }
+      case BinKind::BooleanLiteral: {
+        Maybe<bool> value;
+        for (auto field : fields) {
+            switch (field) {
+              case BinField::Value:
+                MOZ_TRY_EMPLACE(value, readBool());
+                break;
+              default:
+                return raiseInvalidField("BooleanLiteral", field);
+            }
+        }
+
+        // In case of absent optional fields, inject default values.
+        if (!value)
+            value.emplace(false);
+
+        TRY_VAR(result, factory_.newBooleanLiteral(*value, tokenizer_->pos(start)));
+
+        break;
+      }
+      case BinKind::NullLiteral: {
+        MOZ_TRY(checkEmptyTuple(kind, fields));
+        TRY_VAR(result, factory_.newNullLiteral(tokenizer_->pos(start)));
+        break;
+      }
+      case BinKind::NumericLiteral:
+        MOZ_TRY_VAR(result, parseNumericLiteralAux(kind, fields));
+        break;
+
+      case BinKind::RegExpLiteral: {
+        RootedAtom pattern(cx_);
+        Maybe<Chars> flags;
+        for (auto field : fields) {
+            switch (field) {
+              case BinField::Pattern:
+                MOZ_TRY(readString(&pattern));
+                break;
+              case BinField::Flags:
+                MOZ_TRY(readString(flags));
+                break;
+              default:
+                return raiseInvalidField("RegExpLiteral", field);
+            }
+        }
+
+        if (!pattern)
+            return raiseMissingField("RegExpLiteral", BinField::Pattern);
+        if (!flags)
+            return raiseMissingField("RegExpLiteral", BinField::Flags);
+
+        RegExpFlag reflags = NoFlags;
+        for (auto c : *flags) {
+            if (c == 'g' && !(reflags & GlobalFlag))
+                reflags = RegExpFlag(reflags | GlobalFlag);
+            else if (c == 'i' && !(reflags & IgnoreCaseFlag))
+                reflags = RegExpFlag(reflags | IgnoreCaseFlag);
+            else if (c == 'm' && !(reflags & MultilineFlag))
+                reflags = RegExpFlag(reflags | MultilineFlag);
+            else if (c == 'y' && !(reflags & StickyFlag))
+                reflags = RegExpFlag(reflags | StickyFlag);
+            else if (c == 'u' && !(reflags & UnicodeFlag))
+                reflags = RegExpFlag(reflags | UnicodeFlag);
+            else
+                return raiseInvalidEnum("RegExpLiteral", *flags);
+        }
+
+
+        Rooted<RegExpObject*> reobj(cx_);
+        TRY_VAR(reobj, RegExpObject::create(cx_,
+            pattern,
+            reflags,
+            alloc_,
+            TenuredObject));
+
+        TRY_VAR(result, factory_.newRegExp(reobj, tokenizer_->pos(start), *this));
+
+        break;
+      }
+      case BinKind::StringLiteral:
+        MOZ_TRY_VAR(result, parseStringLiteralAux(kind, fields));
+        break;
+
+      case BinKind::ThisExpression: {
+        MOZ_TRY(checkEmptyTuple(kind, fields));
+
+        if (parseContext_->isFunctionBox())
+            parseContext_->functionBox()->usesThis = true;
+
+        TokenPos pos = tokenizer_->pos(start);
+        ParseNode* thisName(nullptr);
+        if (parseContext_->sc()->thisBinding() == ThisBinding::Function)
+            TRY_VAR(thisName, factory_.newName(cx_->names().dotThis, pos, cx_));
+
+        TRY_VAR(result, factory_.newThisLiteral(pos, thisName));
+        break;
+      }
+      case BinKind::ArrayExpression:
+        MOZ_TRY_VAR(result, parseArrayExpressionAux(kind, fields));
+        break;
+
+      case BinKind::ObjectExpression:
+        MOZ_TRY_VAR(result, parseObjectExpressionAux(kind, fields));
+        break;
+
+      case BinKind::FunctionExpression:
+        MOZ_TRY_VAR(result, parseFunctionAux(kind, fields));
+        result->setOp(JSOP_LAMBDA);
+        break;
+
+      case BinKind::UnaryExpression:
+      case BinKind::UpdateExpression: {
+        ParseNode* expr(nullptr);
+        Maybe<Chars> operation;
+        Maybe<bool> prefix; // FIXME: Ignored for unary_expression?
+
+        for (auto field : fields) {
+            switch (field) {
+              case BinField::Operator:
+                MOZ_TRY(readString(operation));
+                break;
+              case BinField::Prefix:
+                MOZ_TRY_EMPLACE(prefix, readBool());
+                break;
+              case BinField::Argument:
+                  // arguments are always parsed *after* operator.
+                  if (operation.isNothing())
+                      return raiseMissingField("UpdateExpression", BinField::Operator);
+                MOZ_TRY_VAR(expr, parseExpression());
+                break;
+              default:
+                return raiseInvalidField("UpdateExpression", field);
+            }
+        }
+
+        if (!expr)
+            return raiseMissingField("UpdateExpression", BinField::Argument);
+        if (operation.isNothing())
+            return raiseMissingField("UpdateExpression", BinField::Operator);
+
+        // In case of absent optional fields, inject default values.
+        if (prefix.isNothing())
+            prefix.emplace(false);
+
+        ParseNodeKind pnk = PNK_LIMIT;
+        if (kind == BinKind::UnaryExpression) {
+            if (*operation == "-") {
+                pnk = PNK_NEG;
+            } else if (*operation == "+") {
+                pnk = PNK_POS;
+            } else if (*operation == "!") {
+                pnk = PNK_NOT;
+            } else if (*operation == "~") {
+                pnk = PNK_BITNOT;
+            } else if (*operation == "typeof") {
+                if (expr->isKind(PNK_NAME))
+                    pnk = PNK_TYPEOFNAME;
+                else
+                    pnk = PNK_TYPEOFEXPR;
+            } else if (*operation == "void") {
+                pnk = PNK_VOID;
+            } else if (*operation == "delete") {
+                switch (expr->getKind()) {
+                  case PNK_NAME:
+                    expr->setOp(JSOP_DELNAME);
+                    pnk = PNK_DELETENAME;
+                    break;
+                  case PNK_DOT:
+                    pnk = PNK_DELETEPROP;
+                    break;
+                  case PNK_ELEM:
+                    pnk = PNK_DELETEELEM;
+                    break;
+                  default:
+                    pnk = PNK_DELETEEXPR;
+                }
+            } else {
+                return raiseInvalidEnum("UnaryOperator", *operation);
+            }
+        } else if (kind == BinKind::UpdateExpression) {
+            if (!expr->isKind(PNK_NAME) && !factory_.isPropertyAccess(expr))
+                return raiseError("Invalid increment/decrement operand"); // FIXME: Shouldn't this be part of the syntax?
+
+            if (*operation == "++") {
+                if (*prefix)
+                    pnk = PNK_PREINCREMENT;
+                else
+                    pnk = PNK_POSTINCREMENT;
+            } else if (*operation == "--") {
+                if (*prefix)
+                    pnk = PNK_PREDECREMENT;
+                else
+                    pnk = PNK_POSTDECREMENT;
+            } else {
+                return raiseInvalidEnum("UpdateOperator", *operation);
+            }
+        }
+
+        TRY_VAR(result, factory_.newUnary(pnk, start, expr));
+
+        break;
+      }
+      case BinKind::BinaryExpression:
+      case BinKind::LogicalExpression: {
+        ParseNode* left(nullptr);
+        ParseNode* right(nullptr);
+        Maybe<Chars> operation;
+        for (auto field : fields) {
+            switch (field) {
+              case BinField::Left:
+                MOZ_TRY_VAR(left, parseExpression());
+                break;
+              case BinField::Right:
+                MOZ_TRY_VAR(right, parseExpression());
+                break;
+              case BinField::Operator:
+                MOZ_TRY(readString(operation));
+                break;
+              default:
+                return raiseInvalidField("LogicalExpression | BinaryExpression", field);
+            }
+        }
+
+        if (!left)
+            return raiseMissingField("LogicalExpression | BinaryExpression", BinField::Left);
+        if (!right)
+            return raiseMissingField("LogicalExpression | BinaryExpression", BinField::Right);
+        if (operation.isNothing())
+            return raiseMissingField("LogicalExpression | BinaryExpression", BinField::Operator);
+
+        // FIXME: Instead of Chars, we should use atoms and comparison
+        // between atom ptr.
+        ParseNodeKind pnk = PNK_LIMIT;
+        if (*operation == "==")
+            pnk = PNK_EQ;
+        else if (*operation == "!=")
+            pnk = PNK_NE;
+        else if (*operation == "===")
+            pnk = PNK_STRICTEQ;
+        else if (*operation == "!==")
+            pnk = PNK_STRICTNE;
+        else if (*operation == "<")
+            pnk = PNK_LT;
+        else if (*operation == "<=")
+            pnk = PNK_LE;
+        else if (*operation == ">")
+            pnk = PNK_GT;
+        else if (*operation == ">=")
+            pnk = PNK_GE;
+        else if (*operation == "<<")
+            pnk = PNK_LSH;
+        else if (*operation == ">>")
+            pnk = PNK_RSH;
+        else if (*operation == ">>>")
+            pnk = PNK_URSH;
+        else if (*operation == "+")
+            pnk = PNK_ADD;
+        else if (*operation == "-")
+            pnk = PNK_SUB;
+        else if (*operation == "*")
+            pnk = PNK_STAR;
+        else if (*operation == "/")
+            pnk = PNK_DIV;
+        else if (*operation == "%")
+            pnk = PNK_MOD;
+        else if (*operation == "|")
+            pnk = PNK_BITOR;
+        else if (*operation == "^")
+            pnk = PNK_BITXOR;
+        else if (*operation == "&")
+            pnk = PNK_BITAND;
+        else if (*operation == "in")
+            pnk = PNK_IN;
+        else if (*operation == "instanceof")
+            pnk = PNK_INSTANCEOF;
+        else if (*operation == "||")
+            pnk = PNK_OR;
+        else if (*operation == "&&")
+            pnk = PNK_AND;
+        else if (*operation == "**")
+            pnk = PNK_POW;
+        else
+            return raiseInvalidEnum("BinaryOperator | LogicalOperator", *operation);
+
+        if (left->isKind(pnk) && pnk != PNK_POW /* PNK_POW is not left-associative */) {
+            // Regroup left-associative operations into lists.
+            left->appendWithoutOrderAssumption(right);
+            result = left;
+        } else {
+            TRY_DECL(list, factory_.newList(pnk, tokenizer_->pos(start)));
+
+            list->appendWithoutOrderAssumption(left);
+            list->appendWithoutOrderAssumption(right);
+            result = list;
+        }
+
+         break;
+      }
+      case BinKind::AssignmentExpression: {
+        ParseNode* left(nullptr);
+        ParseNode* right(nullptr);
+        Maybe<Chars> operation;
+        for (auto field : fields) {
+            switch (field) {
+              case BinField::Left:
+                MOZ_TRY_VAR(left, parseExpression());
+                break;
+              case BinField::Right:
+                MOZ_TRY_VAR(right, parseExpression());
+                break;
+              case BinField::Operator:
+                MOZ_TRY(readString(operation));
+                break;
+              default:
+                return raiseInvalidField("AssignmentExpression", field);
+            }
+        }
+
+        if (!left)
+            return raiseMissingField("AssignmentExpression", BinField::Left);
+        if (!right)
+            return raiseMissingField("AssignmentExpression", BinField::Right);
+        if (operation.isNothing())
+            return raiseMissingField("AssignmentExpression", BinField::Operator);
+
+        // FIXME: Instead of Chars, we should use atoms and comparison
+        // between atom ptr.
+        // FIXME: We should probably turn associative operations into lists.
+        ParseNodeKind pnk = PNK_LIMIT;
+        if (*operation == "=")
+            pnk = PNK_ASSIGN;
+        else if (*operation == "+=")
+            pnk = PNK_ADDASSIGN;
+        else if (*operation == "-=")
+            pnk = PNK_SUBASSIGN;
+        else if (*operation == "*=")
+            pnk = PNK_MULASSIGN;
+        else if (*operation == "/=")
+            pnk = PNK_DIVASSIGN;
+        else if (*operation == "%=")
+            pnk = PNK_MODASSIGN;
+        else if (*operation == "<<=")
+            pnk = PNK_LSHASSIGN;
+        else if (*operation == ">>=")
+            pnk = PNK_RSHASSIGN;
+        else if (*operation == ">>>=")
+            pnk = PNK_URSHASSIGN;
+        else if (*operation == "|=")
+            pnk = PNK_BITORASSIGN;
+        else if (*operation == "^=")
+            pnk = PNK_BITXORASSIGN;
+        else if (*operation == "&=")
+            pnk = PNK_BITANDASSIGN;
+        else
+            return raiseInvalidEnum("AssignmentOperator", *operation);
+
+        TRY_VAR(result, factory_.newAssignment(pnk, left, right));
+
+        break;
+      }
+      case BinKind::BracketExpression:
+      case BinKind::DotExpression:
+        MOZ_TRY_VAR(result, parseMemberExpressionAux(kind, fields));
+
+        break;
+      case BinKind::ConditionalExpression: {
+        ParseNode* test(nullptr);
+        ParseNode* alternate(nullptr);
+        ParseNode* consequent(nullptr);
+
+        for (auto field : fields) {
+            switch (field) {
+              case BinField::Test:
+                MOZ_TRY_VAR(test, parseExpression());
+                break;
+              case BinField::Consequent:
+                MOZ_TRY_VAR(consequent, parseExpression());
+                break;
+              case BinField::Alternate:
+                MOZ_TRY_VAR(alternate, parseExpression());
+                break;
+              default:
+                return raiseInvalidField("ConditionalExpression", field);
+            }
+        }
+
+        if (!test)
+            return raiseMissingField("ConditionalExpression", BinField::Test);
+        if (!consequent)
+            return raiseMissingField("ConditionalExpression", BinField::Consequent);
+        if (!alternate)
+            return raiseMissingField("ConditionalExpression", BinField::Alternate);
+
+        TRY_VAR(result, factory_.newConditional(test, consequent, alternate));
+
+        break;
+      }
+      case BinKind::CallExpression:
+      case BinKind::NewExpression: {
+        ParseNode* callee(nullptr);
+
+        for (auto field : fields) {
+            switch (field) {
+              case BinField::Callee:
+                MOZ_TRY_VAR(callee, parseExpression());
+                break;
+              case BinField::Arguments:
+                MOZ_TRY_VAR(result, parseExpressionList(/* acceptElisions = */ false));
+                break;
+              default:
+                return raiseInvalidField("NewExpression", field);
+            }
+        }
+
+        // In case of absent required fields, fail.
+        if (!callee)
+            return raiseMissingField("NewExpression", BinField::Callee);
+
+        // In case of absent optional fields, inject default values.
+        if (!result)
+            TRY_VAR(result, factory_.newArrayLiteral(start));
+
+        ParseNodeKind pnk =
+            kind == BinKind::CallExpression
+            ? PNK_CALL
+            : PNK_NEW;
+        result->setKind(pnk);
+        result->prepend(callee);
+
+        break;
+      }
+      case BinKind::SequenceExpression: {
+        for (auto field : fields) {
+            switch (field) {
+              case BinField::Expressions:
+                MOZ_TRY_VAR(result, parseExpressionList(/* acceptElisions = */ false));
+                break;
+              default:
+                return raiseInvalidField("SequenceExpression", field);
+            }
+        }
+
+        if (!result)
+            return raiseMissingField("SequenceExpression", BinField::Expression);
+
+        result->setKind(PNK_COMMA);
+        break;
+      }
+      default:
+        return raiseInvalidKind("Expression", kind);
+    }
+
+    return result;
+}
+
+JS::Result<ParseNode*>
+BinASTParser::parseNumericLiteralAux(const BinKind kind, const BinFields& fields)
+{
+    auto start = tokenizer_->offset();
+
+    Maybe<double> value;
+    for (auto field : fields) {
+        switch (field) {
+          case BinField::Value:
+            MOZ_TRY_EMPLACE(value, readNumber());
+            break;
+          default:
+            return raiseInvalidField("NumericLiteral", field);
+        }
+    }
+
+    // In case of absent optional fields, inject default values.
+    if (!value)
+        value.emplace(0);
+
+    TRY_DECL(result, factory_.newNumber(*value, DecimalPoint::HasDecimal, tokenizer_->pos(start)));
+    return result;
+}
+
+JS::Result<ParseNode*>
+BinASTParser::parseStringLiteralAux(const BinKind kind, const BinFields& fields)
+{
+    auto start = tokenizer_->offset();
+
+    RootedAtom value(cx_);
+    for (auto field : fields) {
+        switch (field) {
+          case BinField::Value:
+            MOZ_TRY(readString(&value));
+            break;
+          default:
+            return raiseInvalidField("StringLiteral", field);
+        }
+    }
+
+    if (!value)
+        return raiseMissingField("StringLiteral", BinField::Value);
+
+    TRY_DECL(result, factory_.newStringLiteral(value, tokenizer_->pos(start)));
+    return result;
+}
+
+JS::Result<ParseNode*>
+BinASTParser::parseArrayExpressionAux(const BinKind kind, const BinFields& fields)
+{
+    MOZ_ASSERT(kind == BinKind::ArrayExpression);
+
+    ParseNode* result(nullptr);
+    for (auto field : fields) {
+        switch (field) {
+          case BinField::Elements: {
+            MOZ_TRY_VAR(result, parseExpressionList(/* acceptElisions = */ true));
+            break;
+          }
+          default:
+            return raiseInvalidField("ArrayExpression", field);
+        }
+    }
+
+    // Inject default values for absent fields.
+    if (!result)
+        TRY_VAR(result, factory_.newArrayLiteral(tokenizer_->offset()));
+
+    MOZ_ASSERT(result->isKind(PNK_ARRAY));
+    return result;
+}
+
+JS::Result<ParseNode*>
+BinASTParser::parseObjectExpressionAux(const BinKind kind, const BinFields& fields)
+{
+    MOZ_ASSERT(kind == BinKind::ObjectExpression);
+
+    ParseNode* result(nullptr);
+    for (auto field : fields) {
+        switch (field) {
+          case BinField::Properties: {
+            MOZ_TRY_VAR(result, parseObjectMemberList());
+            break;
+          }
+          default:
+            return raiseInvalidField("Property | Method", field);
+        }
+    }
+
+    if (!result)
+        TRY_VAR(result, factory_.newObjectLiteral(tokenizer_->offset()));
+
+    MOZ_ASSERT(result->isArity(PN_LIST));
+    MOZ_ASSERT(result->isKind(PNK_OBJECT));
+
+#if defined(DEBUG)
+    // Sanity check.
+    for (ParseNode* iter = result->pn_head; iter != nullptr; iter = iter->pn_next) {
+        MOZ_ASSERT(iter->isKind(PNK_COLON));
+        MOZ_ASSERT(iter->pn_left != nullptr);
+        MOZ_ASSERT(iter->pn_right != nullptr);
+    }
+#endif // defined(DEBUG)
+
+    return result;
+}
+
+JS::Result<ParseNode*>
+BinASTParser::parseMemberExpressionAux(const BinKind kind, const BinFields& fields)
+{
+    MOZ_ASSERT(kind == BinKind::DotExpression || kind == BinKind::BracketExpression);
+
+    ParseNode* object(nullptr);
+    ParseNode* property(nullptr);
+
+    for (auto field : fields) {
+        switch (field) {
+          case BinField::Object:
+            MOZ_TRY_VAR(object, parseExpression());
+            break;
+          case BinField::Property:
+            if (kind == BinKind::BracketExpression)
+                MOZ_TRY_VAR(property, parseExpression());
+            else
+                MOZ_TRY_VAR(property, parseIdentifier());
+            break;
+          default:
+            return raiseInvalidField("MemberExpression", field);
+        }
+    }
+
+    // In case of absent required fields, fail.
+    if (!object)
+        return raiseMissingField("MemberExpression", BinField::Object);
+    if (!property)
+        return raiseMissingField("MemberExpression", BinField::Property);
+
+    ParseNode* result(nullptr);
+    if (kind == BinKind::DotExpression) {
+        MOZ_ASSERT(property->isKind(PNK_NAME));
+        PropertyName* name = property->pn_atom->asPropertyName();
+        TRY_VAR(result, factory_.newPropertyAccess(object, name, tokenizer_->offset()));
+    } else {
+        TRY_VAR(result, factory_.newPropertyByValue(object, property, tokenizer_->offset()));
+    }
+
+    return result;
+}
+
+JS::Result<ParseNode*>
+BinASTParser::parseDirectiveList()
+{
+    uint32_t length;
+    AutoList guard(*tokenizer_);
+    TRY(tokenizer_->enterList(length, guard));
+
+    TokenPos pos = tokenizer_->pos();
+    TRY_DECL(result, factory_.newStatementList(pos));
+
+    RootedAtom value(cx_);
+    for (uint32_t i = 0; i < length; ++i) {
+        value = nullptr;
+        MOZ_TRY(readString(&value));
+
+        TRY_DECL(directive, factory_.newStringLiteral(value, pos));
+        factory_.addStatementToList(result, directive);
+    }
+
+    TRY(guard.done());
+    return result;
+}
+
+JS::Result<ParseNode*>
+BinASTParser::parseSwitchCase()
+{
+    const size_t start = tokenizer_->offset();
+
+    BinKind kind;
+    BinFields fields(cx_);
+    AutoTaggedTuple guard(*tokenizer_);
+
+    TRY(tokenizer_->enterTaggedTuple(kind, fields, guard));
+    if (kind != BinKind::SwitchCase)
+        return raiseInvalidKind("SwitchCase", kind);
+
+    ParseNode* test(nullptr); // Optional.
+    ParseNode* statements(nullptr); // Required.
+
+    for (auto field : fields) {
+        switch (field) {
+          case BinField::Test:
+            MOZ_TRY_VAR(test, parseExpression());
+            break;
+          case BinField::Consequent:
+            MOZ_TRY_VAR(statements, parseStatementList());
+            break;
+          default:
+            return raiseInvalidField("SwitchCase", field);
+        }
+    }
+
+    TRY(guard.done());
+    if (!statements)
+        return raiseMissingField("SwitchCase", BinField::Consequent);
+
+    MOZ_ASSERT(statements->isKind(PNK_STATEMENTLIST));
+
+    TRY_DECL(result, factory_.newCaseOrDefault(start, test, statements));
+
+    return result;
+}
+
+JS::Result<ParseNode*>
+BinASTParser::parseCatchClause()
+{
+    ParseContext::Statement stmt(parseContext_, StatementKind::Catch);
+    ParseContext::Scope scope(cx_, parseContext_, usedNames_);
+    TRY(scope.init(parseContext_));
+
+    BinKind kind;
+    BinFields fields(cx_);
+    AutoTaggedTuple guard(*tokenizer_);
+
+    TRY(tokenizer_->enterTaggedTuple(kind, fields, guard));
+    ParseNode* result(nullptr);
+
+    switch (kind) {
+      default:
+        return raiseInvalidKind("CatchClause", kind);
+      case BinKind::CatchClause: {
+        ParseNode* param(nullptr);
+        ParseNode* body(nullptr);
+
+        for (auto field : fields) {
+            switch (field) {
+              case BinField::Param:
+                MOZ_TRY_VAR(param, parsePattern());
+                break;
+              case BinField::Body:
+                MOZ_TRY_VAR(body, parseBlockStatement());
+                break;
+              case BinField::BINJS_Scope:
+                MOZ_TRY(parseAndUpdateCurrentScope());
+                break;
+              default:
+                return raiseInvalidField("CatchClause", field);
+            }
+        }
+
+        if (!param)
+            return raiseMissingField("CatchClause", BinField::Param);
+        if (!body)
+            return raiseMissingField("CatchClause", BinField::Body);
+
+        TRY_DECL(catchClause, factory_.newCatchBlock(param, /* catchGuard = */ nullptr, body));
+        TRY_DECL(bindings, NewLexicalScopeData(cx_, scope, alloc_, parseContext_));
+
+        TRY_VAR(result, factory_.newLexicalScope(*bindings, catchClause));
+      }
+    }
+
+    TRY(guard.done());
+    return result;
+}
+
+JS::Result<ParseNode*>
+BinASTParser::parseArgumentList()
+{
+    uint32_t length;
+    AutoList guard(*tokenizer_);
+
+    TRY(tokenizer_->enterList(length, guard));
+    ParseNode* result = new_<ListNode>(PNK_PARAMSBODY, tokenizer_->pos());
+
+    for (uint32_t i = 0; i < length; ++i) {
+        ParseNode* pattern;
+        MOZ_TRY_VAR(pattern, parsePattern());
+
+        result->appendWithoutOrderAssumption(pattern);
+    }
+
+    TRY(guard.done());
+    return result;
+}
+
+JS::Result<ParseNode*>
+BinASTParser::parseIdentifier()
+{
+    BinKind kind;
+    BinFields fields(cx_);
+    AutoTaggedTuple guard(*tokenizer_);
+
+    TRY(tokenizer_->enterTaggedTuple(kind, fields, guard));
+    ParseNode* result;
+    MOZ_TRY_VAR(result, parseIdentifierAux(kind, fields));
+
+    TRY(guard.done());
+    return result;
+}
+
+JS::Result<ParseNode*>
+BinASTParser::parseIdentifierAux(const BinKind, const BinFields& fields, const bool expectObjectPropertyName /* = false */)
+{
+    const size_t start = tokenizer_->offset();
+
+    RootedAtom id(cx_);
+    for (auto field : fields) {
+        switch (field) {
+          case BinField::Name:
+            MOZ_TRY(readString(&id));
+            break;
+          default:
+            return raiseInvalidField("Identifier", field);
+        }
+    }
+
+    if (!id)
+        return raiseMissingField("Identifier", BinField::Name);
+
+    if (!IsIdentifier(id))
+        return raiseError("Invalid identifier");
+    if (!expectObjectPropertyName && IsKeyword(id))
+        return raiseError("Invalid identifier (keyword)");
+
+    // Once `IsIdentifier` has returned true, we may call `asPropertyName()` without fear.
+    TokenPos pos = tokenizer_->pos(start);
+
+    ParseNode* result;
+    if (expectObjectPropertyName)
+        TRY_VAR(result, factory_.newObjectLiteralPropertyName(id->asPropertyName(), pos));
+    else
+        TRY_VAR(result, factory_.newName(id->asPropertyName(), pos, cx_));
+
+    return result;
+}
+
+
+JS::Result<ParseNode*>
+BinASTParser::parsePattern()
+{
+    BinKind kind;
+    BinFields fields(cx_);
+    AutoTaggedTuple guard(*tokenizer_);
+
+    TRY(tokenizer_->enterTaggedTuple(kind, fields, guard));
+    ParseNode* result;
+    MOZ_TRY_VAR(result, parsePatternAux(kind, fields));
+
+    TRY(guard.done());
+    return result;
+}
+
+JS::Result<ParseNode*>
+BinASTParser::parsePatternAux(const BinKind kind, const BinFields& fields)
+{
+    ParseNode* result;
+    switch (kind) {
+      case BinKind::Identifier:
+        MOZ_TRY_VAR(result, parseIdentifierAux(kind ,fields));
+        break;
+      default:
+        return raiseInvalidKind("Pattern", kind);
+    }
+
+    return result;
+}
+
+JS::Result<ParseNode*>
+BinASTParser::parseObjectMember()
+{
+    BinKind kind;
+    BinFields fields(cx_);
+    AutoTaggedTuple guard(*tokenizer_);
+
+    TRY(tokenizer_->enterTaggedTuple(kind, fields, guard));
+    ParseNode* result(nullptr);
+
+    switch (kind) {
+      case BinKind::ObjectProperty: {
+        ParseNode* key(nullptr);
+        ParseNode* value(nullptr);
+        for (auto field : fields) {
+            switch (field) {
+              case BinField::Key:
+                MOZ_TRY_VAR(key, parseObjectPropertyName());
+                break;
+              case BinField::Value:
+                MOZ_TRY_VAR(value, parseExpression());
+                break;
+              default:
+                return raiseInvalidField("ObjectMember", field);
+            }
+        }
+
+        if (!key)
+            return raiseMissingField("ObjectMember", BinField::Key);
+        if (!value)
+            return raiseMissingField("ObjectMember", BinField::Value);
+
+        if (!factory_.isUsableAsObjectPropertyName(key))
+            return raiseError("ObjectMember key kind");
+
+        TRY_VAR(result, factory_.newObjectMethodOrPropertyDefinition(key, value, AccessorType::None));
+
+        break;
+      }
+      case BinKind::ObjectMethod:
+      case BinKind::ObjectGetter:
+      case BinKind::ObjectSetter:
+        MOZ_TRY_VAR(result, parseFunctionAux(kind, fields));
+
+        if (!result)
+            return raiseEmpty("ObjectMethod");
+
+        MOZ_ASSERT(result->isKind(PNK_COLON));
+        break;
+      default:
+        return raiseInvalidKind("ObjectMember", kind);
+    }
+
+    TRY(guard.done());
+    MOZ_ASSERT(result);
+    return result;
+}
+
+JS::Result<ParseNode*>
+BinASTParser::parseObjectMemberList()
+{
+    uint32_t length;
+    AutoList guard(*tokenizer_);
+
+    auto start = tokenizer_->offset();
+    TRY(tokenizer_->enterList(length, guard));
+
+    TRY_DECL(result, factory_.newObjectLiteral(start));
+
+    for (uint32_t i = 0; i < length; ++i) {
+        ParseNode* keyValue;
+        MOZ_TRY_VAR(keyValue, parseObjectMember());
+        MOZ_ASSERT(keyValue);
+
+        result->appendWithoutOrderAssumption(keyValue);
+    }
+
+    TRY(guard.done());
+    return result;
+}
+
+
+JS::Result<Ok>
+BinASTParser::checkEmptyTuple(const BinKind kind, const BinFields& fields)
+{
+    if (fields.length() != 0)
+        return raiseInvalidField(describeBinKind(kind), fields[0]);
+
+    return Ok();
+}
+
+
+JS::Result<Ok>
+BinASTParser::readString(MutableHandleAtom out)
+{
+    MOZ_ASSERT(!out);
+
+    Maybe<Chars> string;
+    MOZ_TRY(readString(string));
+    MOZ_ASSERT(string);
+
+    RootedAtom atom(cx_);
+    TRY_VAR(atom, Atomize(cx_, (const char*)string->begin(), string->length()));
+
+    out.set(Move(atom));
+    return Ok();
+}
+
+JS::Result<ParseNode*>
+BinASTParser::parsePropertyName()
+{
+    RootedAtom atom(cx_);
+    MOZ_TRY(readString(&atom));
+
+    TokenPos pos = tokenizer_->pos();
+
+    ParseNode* result;
+
+    // If the atom matches an index (e.g. "3"), we need to normalize the
+    // propertyName to ensure that it has the same representation as
+    // the numeric index (e.g. 3).
+    uint32_t index;
+    if (atom->isIndex(&index))
+        TRY_VAR(result, factory_.newNumber(index, NoDecimal, pos));
+    else
+        TRY_VAR(result, factory_.newStringLiteral(atom, pos));
+
+    return result;
+}
+
+JS::Result<Ok>
+BinASTParser::readString(Maybe<Chars>& out)
+{
+    MOZ_ASSERT(out.isNothing());
+    Chars result(cx_);
+    TRY(tokenizer_->readChars(result));
+
+    out.emplace(Move(result));
+    return Ok();
+}
+
+JS::Result<double>
+BinASTParser::readNumber()
+{
+    double result;
+    TRY(tokenizer_->readDouble(result));
+
+    return result;
+}
+
+JS::Result<bool>
+BinASTParser::readBool()
+{
+    bool result;
+    TRY(tokenizer_->readBool(result));
+
+    return result;
+}
+
+mozilla::GenericErrorResult<JS::Error&>
+BinASTParser::raiseInvalidKind(const char* superKind, const BinKind kind)
+{
+    Sprinter out(cx_);
+    TRY(out.init());
+    TRY(out.printf("In %s, invalid kind %s", superKind, describeBinKind(kind)));
+    return raiseError(out.string());
+}
+
+mozilla::GenericErrorResult<JS::Error&>
+BinASTParser::raiseInvalidField(const char* kind, const BinField field)
+{
+    Sprinter out(cx_);
+    TRY(out.init());
+    TRY(out.printf("In %s, invalid field '%s'", kind, describeBinField(field)));
+    return raiseError(out.string());
+}
+
+mozilla::GenericErrorResult<JS::Error&>
+BinASTParser::raiseInvalidEnum(const char* kind, const Chars& value)
+{
+    // We don't trust the actual chars of `value` to be properly formatted anything, so let's not use
+    // them anywhere.
+    return raiseError("Invalid enum");
+}
+
+mozilla::GenericErrorResult<JS::Error&>
+BinASTParser::raiseMissingField(const char* kind, const BinField field)
+{
+    Sprinter out(cx_);
+    TRY(out.init());
+    TRY(out.printf("In %s, missing field '%s'", kind, describeBinField(field)));
+
+    return raiseError(out.string());
+}
+
+mozilla::GenericErrorResult<JS::Error&>
+BinASTParser::raiseEmpty(const char* description)
+{
+    Sprinter out(cx_);
+    TRY(out.init());
+    TRY(out.printf("Empty %s", description));
+
+    return raiseError(out.string());
+}
+
+mozilla::GenericErrorResult<JS::Error&>
+BinASTParser::raiseOOM()
+{
+    ReportOutOfMemory(cx_);
+    return cx_->alreadyReportedError();
+}
+
+mozilla::GenericErrorResult<JS::Error&>
+BinASTParser::raiseError(BinKind kind, const char* description)
+{
+    Sprinter out(cx_);
+    TRY(out.init());
+    TRY(out.printf("In %s, ", description));
+    MOZ_ALWAYS_FALSE(tokenizer_->raiseError(out.string()));
+
+    return cx_->alreadyReportedError();
+}
+
+mozilla::GenericErrorResult<JS::Error&>
+BinASTParser::raiseError(const char* description)
+{
+    MOZ_ALWAYS_FALSE(tokenizer_->raiseError(description));
+    return cx_->alreadyReportedError();
+}
+
+void
+BinASTParser::poison()
+{
+    tokenizer_.reset();
+}
+
+void
+BinASTParser::reportErrorNoOffsetVA(unsigned errorNumber, va_list args)
+{
+    ErrorMetadata metadata;
+    metadata.filename = getFilename();
+    metadata.lineNumber = 0;
+    metadata.columnNumber = offset();
+    ReportCompileError(cx_, Move(metadata), nullptr, JSREPORT_ERROR, errorNumber, args);
+}
+
+bool
+BinASTParser::hasUsedName(HandlePropertyName name)
+{
+    if (UsedNamePtr p = usedNames_.lookup(name))
+        return p->value().isUsedInScript(parseContext_->scriptId());
+
+    return false;
+}
+
+void
+TraceBinParser(JSTracer* trc, AutoGCRooter* parser)
+{
+    static_cast<BinASTParser*>(parser)->trace(trc);
+}
+
+} // namespace frontend
+} // namespace js
+
+
+// #undef everything, to avoid collisions with unified builds.
+
+#undef TRY
+#undef TRY_VAR
+#undef TRY_DECL
+#undef TRY_EMPL
+#undef MOZ_TRY_EMPLACE
new file mode 100644
--- /dev/null
+++ b/js/src/frontend/BinSource.h
@@ -0,0 +1,278 @@
+#ifndef frontend_BinSource_h
+#define frontend_BinSource_h
+
+/**
+ * A Binary AST parser.
+ *
+ * At the time of this writing, this parser implements the grammar of ES5
+ * and trusts its input (in particular, variable declarations).
+ */
+
+#include "mozilla/Maybe.h"
+
+#include "frontend/BinTokenReaderTester.h"
+#include "frontend/FullParseHandler.h"
+#include "frontend/ParseContext.h"
+#include "frontend/ParseNode.h"
+#include "frontend/SharedContext.h"
+
+#include "js/GCHashTable.h"
+#include "js/GCVector.h"
+#include "js/Result.h"
+
+namespace js {
+namespace frontend {
+
+class BinASTParser;
+
+/**
+ * The parser for a Binary AST.
+ *
+ * By design, this parser never needs to backtrack or look ahead. Errors are not
+ * recoverable.
+ */
+class BinASTParser : private JS::AutoGCRooter, public ErrorReporter
+{
+    using Names = JS::GCVector<JSString*, 8>;
+    using Tokenizer = BinTokenReaderTester;
+    using Chars = Tokenizer::Chars;
+
+  public:
+    BinASTParser(JSContext* cx, LifoAlloc& alloc, UsedNameTracker& usedNames, const JS::ReadOnlyCompileOptions& options)
+        : AutoGCRooter(cx, BINPARSER)
+        , traceListHead_(nullptr)
+        , options_(options)
+        , cx_(cx)
+        , alloc_(alloc)
+        , nodeAlloc_(cx, alloc)
+        , keepAtoms_(cx)
+        , parseContext_(nullptr)
+        , usedNames_(usedNames)
+        , factory_(cx, alloc, nullptr, SourceKind::Binary)
+    {
+         cx_->frontendCollectionPool().addActiveCompilation();
+         tempPoolMark_ = alloc.mark();
+    }
+    ~BinASTParser()
+    {
+        alloc_.release(tempPoolMark_);
+
+        /*
+         * The parser can allocate enormous amounts of memory for large functions.
+         * Eagerly free the memory now (which otherwise won't be freed until the
+         * next GC) to avoid unnecessary OOMs.
+         */
+        alloc_.freeAllIfHugeAndUnused();
+
+        cx_->frontendCollectionPool().removeActiveCompilation();
+    }
+
+    /**
+     * Parse a buffer, returning a node (which may be nullptr) in case of success
+     * or Nothing() in case of error.
+     *
+     * The instance of `ParseNode` MAY NOT survive the `BinASTParser`. Indeed,
+     * destruction of the `BinASTParser` will also destroy the `ParseNode`.
+     *
+     * In case of error, the parser reports the JS error.
+     */
+    JS::Result<ParseNode*> parse(const uint8_t* start, const size_t length);
+    JS::Result<ParseNode*> parse(const Vector<uint8_t>& data);
+
+  private:
+    MOZ_MUST_USE JS::Result<ParseNode*> parseAux(const uint8_t* start, const size_t length);
+
+    // --- Raise errors.
+    //
+    // These methods return a (failed) JS::Result for convenience.
+
+    MOZ_MUST_USE mozilla::GenericErrorResult<JS::Error&> raiseInvalidKind(const char* superKind, const BinKind kind);
+    MOZ_MUST_USE mozilla::GenericErrorResult<JS::Error&> raiseInvalidField(const char* kind, const BinField field);
+    MOZ_MUST_USE mozilla::GenericErrorResult<JS::Error&> raiseInvalidEnum(const char* kind, const Chars& value);
+    MOZ_MUST_USE mozilla::GenericErrorResult<JS::Error&> raiseMissingField(const char* kind, const BinField field);
+    MOZ_MUST_USE mozilla::GenericErrorResult<JS::Error&> raiseEmpty(const char* description);
+    MOZ_MUST_USE mozilla::GenericErrorResult<JS::Error&> raiseOOM();
+    MOZ_MUST_USE mozilla::GenericErrorResult<JS::Error&> raiseError(const char* description);
+    MOZ_MUST_USE mozilla::GenericErrorResult<JS::Error&> raiseError(BinKind kind, const char* description);
+
+    // Ensure that this parser will never be used again.
+    void poison();
+
+    // --- Parse full nodes (methods are sorted by alphabetical order)
+    //
+    // These method may NEVER return `nullptr`. // FIXME: We can probably optimize Result<> based on this.
+
+    MOZ_MUST_USE JS::Result<ParseNode*> parseBlockStatement();
+    MOZ_MUST_USE JS::Result<ParseNode*> parseCatchClause();
+    MOZ_MUST_USE JS::Result<ParseNode*> parseExpression();
+    MOZ_MUST_USE JS::Result<ParseNode*> parseForInit();
+    MOZ_MUST_USE JS::Result<ParseNode*> parseForInInit();
+    MOZ_MUST_USE JS::Result<ParseNode*> parseIdentifier();
+    MOZ_MUST_USE JS::Result<ParseNode*> parseObjectPropertyName();
+    MOZ_MUST_USE JS::Result<ParseNode*> parseObjectMember();
+    MOZ_MUST_USE JS::Result<ParseNode*> parsePattern(); // Parse a *binding* pattern.
+    MOZ_MUST_USE JS::Result<ParseNode*> parsePropertyName();
+    MOZ_MUST_USE JS::Result<ParseNode*> parseProgram();
+    MOZ_MUST_USE JS::Result<ParseNode*> parseStatement();
+    MOZ_MUST_USE JS::Result<ParseNode*> parseSwitchCase();
+    MOZ_MUST_USE JS::Result<ParseNode*> parseVariableDeclarator();
+
+
+    // --- Parse lists of nodes (methods are sorted by alphabetical order)
+
+    MOZ_MUST_USE JS::Result<ParseNode*> parseArgumentList();
+    MOZ_MUST_USE JS::Result<ParseNode*> parseDirectiveList();
+    MOZ_MUST_USE JS::Result<ParseNode*> parseExpressionList(bool acceptElisions);
+
+    // Returns a list of PNK_COLON.
+    MOZ_MUST_USE JS::Result<ParseNode*> parseObjectMemberList();
+
+    MOZ_MUST_USE JS::Result<ParseNode*> parseStatementList();
+    MOZ_MUST_USE JS::Result<ParseNode*> parseSwitchCaseList();
+
+    // --- Parse the contents of a node whose kind has already been determined.
+
+    MOZ_MUST_USE JS::Result<ParseNode*> parseArrayExpressionAux(const BinKind kind, const Tokenizer::BinFields& fields);
+    MOZ_MUST_USE JS::Result<ParseNode*> parseBreakOrContinueStatementAux(const BinKind kind, const Tokenizer::BinFields& fields);
+    MOZ_MUST_USE JS::Result<ParseNode*> parseBlockStatementAux(const BinKind kind, const Tokenizer::BinFields& fields);
+    MOZ_MUST_USE JS::Result<ParseNode*> parseExpressionStatementAux(const BinKind kind, const Tokenizer::BinFields& fields);
+    MOZ_MUST_USE JS::Result<ParseNode*> parseExpressionAux(const BinKind kind, const Tokenizer::BinFields& fields);
+    MOZ_MUST_USE JS::Result<ParseNode*> parseFunctionAux(const BinKind kind, const Tokenizer::BinFields& fields);
+    MOZ_MUST_USE JS::Result<ParseNode*> parseIdentifierAux(const BinKind, const Tokenizer::BinFields& fields, const bool expectObjectPropertyName = false);
+    MOZ_MUST_USE JS::Result<ParseNode*> parseMemberExpressionAux(const BinKind kind, const Tokenizer::BinFields& fields);
+    MOZ_MUST_USE JS::Result<ParseNode*> parseNumericLiteralAux(const BinKind kind, const Tokenizer::BinFields& fields);
+    MOZ_MUST_USE JS::Result<ParseNode*> parseObjectExpressionAux(const BinKind kind, const Tokenizer::BinFields& fields);
+    MOZ_MUST_USE JS::Result<ParseNode*> parsePatternAux(const BinKind kind, const Tokenizer::BinFields& fields);
+    MOZ_MUST_USE JS::Result<ParseNode*> parseStringLiteralAux(const BinKind kind, const Tokenizer::BinFields& fields);
+    MOZ_MUST_USE JS::Result<ParseNode*> parseStatementAux(const BinKind kind, const Tokenizer::BinFields& fields);
+    MOZ_MUST_USE JS::Result<ParseNode*> parseVariableDeclarationAux(const BinKind kind, const Tokenizer::BinFields& fields);
+
+    // --- Auxiliary parsing functions that may have a side-effect on the parser but do not return a node.
+
+    MOZ_MUST_USE JS::Result<Ok> checkEmptyTuple(const BinKind kind, const Tokenizer::BinFields& fields);
+    MOZ_MUST_USE JS::Result<Ok> parseElisionAux(const BinKind kind, const Tokenizer::BinFields& fields);
+
+    // Parse full scope information to the current innermost scope.
+    MOZ_MUST_USE JS::Result<Ok> parseAndUpdateCurrentScope();
+    // Parse full scope information to a specific var scope / let scope combination.
+    MOZ_MUST_USE JS::Result<Ok> parseAndUpdateScope(ParseContext::Scope& varScope, ParseContext::Scope& letScope);
+    // Parse a list of names and add it to a given scope.
+    MOZ_MUST_USE JS::Result<Ok> parseAndUpdateScopeNames(ParseContext::Scope& scope, DeclarationKind kind);
+    MOZ_MUST_USE JS::Result<Ok> parseStringList(MutableHandle<Maybe<Names>> out);
+
+    // --- Utilities.
+
+    MOZ_MUST_USE JS::Result<ParseNode*> appendDirectivesToBody(ParseNode* body, ParseNode* directives);
+
+    // Read a string as a `Chars`.
+    MOZ_MUST_USE JS::Result<Ok> readString(Maybe<Chars>& out);
+    MOZ_MUST_USE JS::Result<Ok> readString(MutableHandleAtom out);
+    MOZ_MUST_USE JS::Result<bool> readBool();
+    MOZ_MUST_USE JS::Result<double> readNumber();
+
+    const ReadOnlyCompileOptions& options() const override {
+        return this->options_;
+    }
+
+    // Names
+
+
+    bool hasUsedName(HandlePropertyName name);
+
+    // --- GC.
+
+    void trace(JSTracer* trc) {
+        ObjectBox::TraceList(trc, traceListHead_);
+    }
+
+
+  public:
+    ObjectBox* newObjectBox(JSObject* obj) {
+        MOZ_ASSERT(obj);
+
+        /*
+         * We use JSContext.tempLifoAlloc to allocate parsed objects and place them
+         * on a list in this Parser to ensure GC safety. Thus the tempLifoAlloc
+         * arenas containing the entries must be alive until we are done with
+         * scanning, parsing and code generation for the whole script or top-level
+         * function.
+         */
+
+         ObjectBox* objbox = alloc_.new_<ObjectBox>(obj, traceListHead_);
+         if (!objbox) {
+             ReportOutOfMemory(cx_);
+             return nullptr;
+        }
+
+        traceListHead_ = objbox;
+
+        return objbox;
+    }
+
+    ParseNode* allocParseNode(size_t size) {
+        MOZ_ASSERT(size == sizeof(ParseNode));
+        return static_cast<ParseNode*>(nodeAlloc_.allocNode());
+    }
+
+    JS_DECLARE_NEW_METHODS(new_, allocParseNode, inline)
+
+  private: // Implement ErrorReporter
+
+    virtual void lineAndColumnAt(size_t offset, uint32_t* line, uint32_t* column) const override {
+        *line = 0;
+        *column = offset;
+    }
+    virtual void currentLineAndColumn(uint32_t* line, uint32_t* column) const override {
+        *line = 0;
+        *column = offset();
+    }
+    size_t offset() const {
+        if (tokenizer_.isSome())
+            return tokenizer_->offset();
+
+        return 0;
+    }
+    virtual bool hasTokenizationStarted() const override {
+        return tokenizer_.isSome();
+    }
+    virtual void reportErrorNoOffsetVA(unsigned errorNumber, va_list args) override;
+    virtual const char* getFilename() const override {
+        return this->options_.filename();
+    }
+
+    ObjectBox* traceListHead_;
+    const ReadOnlyCompileOptions& options_;
+    JSContext* cx_;
+    LifoAlloc& alloc_;
+    LifoAlloc::Mark tempPoolMark_;
+    ParseNodeAllocator nodeAlloc_;
+
+    // Root atoms and objects allocated for the parse tree.
+    AutoKeepAtoms keepAtoms_;
+
+    // The current ParseContext, holding directives, etc.
+    ParseContext* parseContext_;
+    UsedNameTracker& usedNames_;
+    Maybe<Tokenizer> tokenizer_;
+    FullParseHandler factory_;
+
+    friend class BinParseContext;
+
+    // Needs access to AutoGCRooter.
+    friend void TraceBinParser(JSTracer* trc, AutoGCRooter* parser);
+};
+
+class BinParseContext : public ParseContext
+{
+  public:
+    BinParseContext(JSContext* cx, BinASTParser* parser, SharedContext* sc, Directives* newDirectives)
+        : ParseContext(cx, parser->parseContext_, sc, *parser,
+                       parser->usedNames_, newDirectives, /* isFull = */ true)
+    { }
+};
+
+
+} // namespace frontend
+} // namespace js
+
+#endif // frontend_BinSource_h
\ No newline at end of file
--- a/js/src/frontend/BinTokenReaderTester.cpp
+++ b/js/src/frontend/BinTokenReaderTester.cpp
@@ -26,18 +26,17 @@ BinTokenReaderTester::BinTokenReaderTest
     , stop_(chars.end())
     , latestKnownGoodPos_(0)
 { }
 
 bool
 BinTokenReaderTester::raiseError(const char* description)
 {
     MOZ_ASSERT(!cx_->isExceptionPending());
-    TokenPos pos;
-    latestTokenPos(pos);
+    TokenPos pos = this->pos();
     JS_ReportErrorASCII(cx_, "BinAST parsing error: %s at offsets %u => %u",
                         description, pos.begin, pos.end);
     return false;
 }
 
 bool
 BinTokenReaderTester::readBuf(uint8_t* bytes, uint32_t len)
 {
@@ -411,22 +410,30 @@ BinTokenReaderTester::updateLatestKnownG
 }
 
 size_t
 BinTokenReaderTester::offset() const
 {
     return latestKnownGoodPos_;
 }
 
-void
-BinTokenReaderTester::latestTokenPos(TokenPos& pos)
+TokenPos
+BinTokenReaderTester::pos()
 {
-    pos.begin = latestKnownGoodPos_;
+    return pos(latestKnownGoodPos_);
+}
+
+TokenPos
+BinTokenReaderTester::pos(size_t start)
+{
+    TokenPos pos;
+    pos.begin = start;
     pos.end = current_ - start_;
     MOZ_ASSERT(pos.end >= pos.begin);
+    return pos;
 }
 
 void
 BinTokenReaderTester::AutoBase::init()
 {
     initialized_ = true;
 }
 
--- a/js/src/frontend/BinTokenReaderTester.h
+++ b/js/src/frontend/BinTokenReaderTester.h
@@ -176,17 +176,18 @@ class MOZ_STACK_CLASS BinTokenReaderTest
      *
      * @return out If the header of the tuple is invalid.
      */
     MOZ_MUST_USE bool enterUntaggedTuple(AutoTuple& guard);
 
     /**
      * Return the position of the latest token.
      */
-    void latestTokenPos(TokenPos& out);
+    TokenPos pos();
+    TokenPos pos(size_t startOffset);
     size_t offset() const;
 
     /**
      * Raise an error.
      *
      * Once `raiseError` has been called, the tokenizer is poisoned.
      */
     MOZ_MUST_USE bool raiseError(const char* description);
--- a/js/src/frontend/BytecodeCompiler.h
+++ b/js/src/frontend/BytecodeCompiler.h
@@ -119,20 +119,23 @@ IsIdentifier(const char16_t* chars, size
 /* True if str is a keyword. Defined in TokenStream.cpp. */
 bool
 IsKeyword(JSLinearString* str);
 
 /* Trace all GC things reachable from parser. Defined in Parser.cpp. */
 void
 TraceParser(JSTracer* trc, JS::AutoGCRooter* parser);
 
+#if defined(JS_BUILD_BINAST)
+
 /* Trace all GC things reachable from binjs parser. Defined in BinSource.cpp. */
 void
 TraceBinParser(JSTracer* trc, JS::AutoGCRooter* parser);
 
+#endif // defined(JS_BUILD_BINAST)
 
 class MOZ_STACK_CLASS AutoFrontendTraceLog
 {
 #ifdef JS_TRACE_LOGGING
     TraceLoggerThread* logger_;
     mozilla::Maybe<TraceLoggerEvent> frontendEvent_;
     mozilla::Maybe<AutoTraceLog> frontendLog_;
     mozilla::Maybe<AutoTraceLog> typeLog_;
--- a/js/src/frontend/FullParseHandler.h
+++ b/js/src/frontend/FullParseHandler.h
@@ -16,16 +16,23 @@
 #include "frontend/SharedContext.h"
 
 namespace js {
 
 class RegExpObject;
 
 namespace frontend {
 
+enum class SourceKind {
+    // We are parsing from a text source (Parser.h)
+    Text,
+    // We are parsing from a binary source (BinSource.h)
+    Binary,
+};
+
 // Parse handler used when generating a full parse tree for all code which the
 // parser encounters.
 class FullParseHandler
 {
     ParseNodeAllocator allocator;
 
     ParseNode* allocParseNode(size_t size) {
         MOZ_ASSERT(size == sizeof(ParseNode));
@@ -36,16 +43,18 @@ class FullParseHandler
      * If this is a full parse to construct the bytecode for a function that
      * was previously lazily parsed, that lazy function and the current index
      * into its inner functions. We do not want to reparse the inner functions.
      */
     const Rooted<LazyScript*> lazyOuterFunction_;
     size_t lazyInnerFunctionIndex;
     size_t lazyClosedOverBindingIndex;
 
+    const SourceKind sourceKind_;
+
   public:
     /* new_ methods for creating parse nodes. These report OOM on context. */
     JS_DECLARE_NEW_METHODS(new_, allocParseNode, inline)
 
     typedef ParseNode* Node;
 
     bool isPropertyAccess(ParseNode* node) {
         return node->isKind(PNK_DOT) || node->isKind(PNK_ELEM);
@@ -63,25 +72,34 @@ class FullParseHandler
     static bool isParenthesizedDestructuringPattern(ParseNode* node) {
         // Technically this isn't a destructuring pattern at all -- the grammar
         // doesn't treat it as such.  But we need to know when this happens to
         // consider it a SyntaxError rather than an invalid-left-hand-side
         // ReferenceError.
         return node->isInParens() && (node->isKind(PNK_OBJECT) || node->isKind(PNK_ARRAY));
     }
 
-    FullParseHandler(JSContext* cx, LifoAlloc& alloc, LazyScript* lazyOuterFunction)
+    FullParseHandler(JSContext* cx, LifoAlloc& alloc, LazyScript* lazyOuterFunction,
+                     SourceKind kind = SourceKind::Text)
       : allocator(cx, alloc),
         lazyOuterFunction_(cx, lazyOuterFunction),
         lazyInnerFunctionIndex(0),
-        lazyClosedOverBindingIndex(0)
+        lazyClosedOverBindingIndex(0),
+        sourceKind_(SourceKind::Text)
     {}
 
     static ParseNode* null() { return nullptr; }
 
+    // The FullParseHandler may be used to create nodes for text sources
+    // (from Parser.h) or for binary sources (from BinSource.h). In the latter
+    // case, some common assumptions on offsets are incorrect, e.g. in `a + b`,
+    // `a`, `b` and `+` may be stored in any order. We use `sourceKind()`
+    // to determine whether we need to check these assumptions.
+    SourceKind sourceKind() const { return sourceKind_; }
+
     ParseNode* freeTree(ParseNode* pn) { return allocator.freeTree(pn); }
     void prepareNodeForMutation(ParseNode* pn) { return allocator.prepareNodeForMutation(pn); }
 
     ParseNode* newName(PropertyName* name, const TokenPos& pos, JSContext* cx)
     {
         return new_<NameNode>(PNK_NAME, JSOP_GETNAME, name, pos);
     }
 
@@ -225,42 +243,39 @@ class FullParseHandler
 
     MOZ_MUST_USE bool addElision(ParseNode* literal, const TokenPos& pos) {
         MOZ_ASSERT(literal->isKind(PNK_ARRAY));
         MOZ_ASSERT(literal->isArity(PN_LIST));
 
         ParseNode* elision = new_<NullaryNode>(PNK_ELISION, pos);
         if (!elision)
             return false;
-
-        literal->append(elision);
+        addList(/* list = */ literal, /* child = */ elision);
         literal->pn_xflags |= PNX_ARRAYHOLESPREAD | PNX_NONCONST;
         return true;
     }
 
     MOZ_MUST_USE bool addSpreadElement(ParseNode* literal, uint32_t begin, ParseNode* inner) {
         MOZ_ASSERT(literal->isKind(PNK_ARRAY));
         MOZ_ASSERT(literal->isArity(PN_LIST));
 
         ParseNode* spread = newSpread(begin, inner);
         if (!spread)
             return false;
-
-        literal->append(spread);
+        addList(/* list = */ literal, /* child = */ spread);
         literal->pn_xflags |= PNX_ARRAYHOLESPREAD | PNX_NONCONST;
         return true;
     }
 
     void addArrayElement(ParseNode* literal, ParseNode* element) {
         MOZ_ASSERT(literal->isArity(PN_LIST));
 
         if (!element->isConstant())
             literal->pn_xflags |= PNX_NONCONST;
-
-        literal->append(element);
+        addList(/* list = */ literal, /* child = */ element);
     }
 
     ParseNode* newCall(const TokenPos& pos) {
         return new_<ListNode>(PNK_CALL, JSOP_CALL, pos);
     }
 
     ParseNode* newSuperCall(ParseNode* callee) {
         return new_<ListNode>(PNK_SUPERCALL, JSOP_SUPERCALL, callee);
@@ -289,106 +304,97 @@ class FullParseHandler
         return new_<BinaryNode>(PNK_NEWTARGET, JSOP_NOP, newHolder, targetHolder);
     }
     ParseNode* newPosHolder(const TokenPos& pos) {
         return new_<NullaryNode>(PNK_POSHOLDER, pos);
     }
     ParseNode* newSuperBase(ParseNode* thisName, const TokenPos& pos) {
         return new_<UnaryNode>(PNK_SUPERBASE, pos, thisName);
     }
-
+    ParseNode* newCatchBlock(ParseNode* catchName, ParseNode* catchGuard, ParseNode* catchBody) {
+        return new_<TernaryNode>(PNK_CATCH, catchName, catchGuard, catchBody);
+    }
     MOZ_MUST_USE bool addPrototypeMutation(ParseNode* literal, uint32_t begin, ParseNode* expr) {
         MOZ_ASSERT(literal->isKind(PNK_OBJECT));
         MOZ_ASSERT(literal->isArity(PN_LIST));
 
         // Object literals with mutated [[Prototype]] are non-constant so that
         // singleton objects will have Object.prototype as their [[Prototype]].
         setListFlag(literal, PNX_NONCONST);
 
         ParseNode* mutation = newUnary(PNK_MUTATEPROTO, begin, expr);
         if (!mutation)
             return false;
-        literal->append(mutation);
+        addList(/* list = */ literal, /* child = */ mutation);
         return true;
     }
 
     MOZ_MUST_USE bool addPropertyDefinition(ParseNode* literal, ParseNode* key, ParseNode* val) {
         MOZ_ASSERT(literal->isKind(PNK_OBJECT));
         MOZ_ASSERT(literal->isArity(PN_LIST));
-        MOZ_ASSERT(key->isKind(PNK_NUMBER) ||
-                   key->isKind(PNK_OBJECT_PROPERTY_NAME) ||
-                   key->isKind(PNK_STRING) ||
-                   key->isKind(PNK_COMPUTED_NAME));
+        MOZ_ASSERT(isUsableAsObjectPropertyName(key));
 
         ParseNode* propdef = newBinary(PNK_COLON, key, val, JSOP_INITPROP);
         if (!propdef)
             return false;
-        literal->append(propdef);
+        addList(/* list = */ literal, /* child = */ propdef);
         return true;
     }
 
     MOZ_MUST_USE bool addShorthand(ParseNode* literal, ParseNode* name, ParseNode* expr) {
         MOZ_ASSERT(literal->isKind(PNK_OBJECT));
         MOZ_ASSERT(literal->isArity(PN_LIST));
         MOZ_ASSERT(name->isKind(PNK_OBJECT_PROPERTY_NAME));
         MOZ_ASSERT(expr->isKind(PNK_NAME));
         MOZ_ASSERT(name->pn_atom == expr->pn_atom);
 
         setListFlag(literal, PNX_NONCONST);
         ParseNode* propdef = newBinary(PNK_SHORTHAND, name, expr, JSOP_INITPROP);
         if (!propdef)
             return false;
-        literal->append(propdef);
+        addList(/* list = */ literal, /* child = */ propdef);
         return true;
     }
 
     MOZ_MUST_USE bool addSpreadProperty(ParseNode* literal, uint32_t begin, ParseNode* inner) {
         MOZ_ASSERT(literal->isKind(PNK_OBJECT));
         MOZ_ASSERT(literal->isArity(PN_LIST));
 
         setListFlag(literal, PNX_NONCONST);
         ParseNode* spread = newSpread(begin, inner);
         if (!spread)
             return false;
-        literal->append(spread);
+        addList(/* list = */ literal, /* child = */ spread);
         return true;
     }
 
     MOZ_MUST_USE bool addObjectMethodDefinition(ParseNode* literal, ParseNode* key, ParseNode* fn,
                                                 AccessorType atype)
     {
-        MOZ_ASSERT(literal->isKind(PNK_OBJECT));
         MOZ_ASSERT(literal->isArity(PN_LIST));
-        MOZ_ASSERT(key->isKind(PNK_NUMBER) ||
-                   key->isKind(PNK_OBJECT_PROPERTY_NAME) ||
-                   key->isKind(PNK_STRING) ||
-                   key->isKind(PNK_COMPUTED_NAME));
+        literal->pn_xflags |= PNX_NONCONST;
 
-        ParseNode* propdef = newBinary(PNK_COLON, key, fn, AccessorTypeToJSOp(atype));
+        ParseNode* propdef = newObjectMethodOrPropertyDefinition(key, fn, atype);
         if (!propdef)
             return false;
 
-        literal->append(propdef);
-        literal->pn_xflags |= PNX_NONCONST;
+        addList(/* list = */ literal, /* child = */ propdef);
         return true;
     }
 
     MOZ_MUST_USE bool addClassMethodDefinition(ParseNode* methodList, ParseNode* key, ParseNode* fn,
                                                AccessorType atype, bool isStatic)
     {
         MOZ_ASSERT(methodList->isKind(PNK_CLASSMETHODLIST));
-        MOZ_ASSERT(key->isKind(PNK_NUMBER) ||
-                   key->isKind(PNK_OBJECT_PROPERTY_NAME) ||
-                   key->isKind(PNK_STRING) ||
-                   key->isKind(PNK_COMPUTED_NAME));
+        MOZ_ASSERT(isUsableAsObjectPropertyName(key));
 
         ParseNode* classMethod = new_<ClassMethod>(key, fn, AccessorTypeToJSOp(atype), isStatic);
         if (!classMethod)
             return false;
-        methodList->append(classMethod);
+        addList(/* list = */ methodList, /* child = */ classMethod);
         return true;
     }
 
     ParseNode* newInitialYieldExpression(uint32_t begin, ParseNode* gen) {
         TokenPos pos(begin, begin + 1);
         return new_<UnaryNode>(PNK_INITIALYIELD, pos, gen);
     }
 
@@ -417,17 +423,17 @@ class FullParseHandler
         while (stmt->isKind(PNK_LABEL))
             stmt = stmt->as<LabeledStatement>().statement();
         return stmt->isKind(PNK_FUNCTION);
     }
 
     void addStatementToList(ParseNode* list, ParseNode* stmt) {
         MOZ_ASSERT(list->isKind(PNK_STATEMENTLIST));
 
-        list->append(stmt);
+        addList(/* list = */ list, /* child = */ stmt);
 
         if (isFunctionStmt(stmt)) {
             // PNX_FUNCDEFS notifies the emitter that the block contains
             // body-level function definitions that should be processed
             // before the rest of nodes.
             list->pn_xflags |= PNX_FUNCDEFS;
         }
     }
@@ -437,35 +443,38 @@ class FullParseHandler
         list->pn_pos.end = pos.end;
     }
 
     void addCaseStatementToList(ParseNode* list, ParseNode* casepn) {
         MOZ_ASSERT(list->isKind(PNK_STATEMENTLIST));
         MOZ_ASSERT(casepn->isKind(PNK_CASE));
         MOZ_ASSERT(casepn->pn_right->isKind(PNK_STATEMENTLIST));
 
-        list->append(casepn);
+        addList(/* list = */ list, /* child = */ casepn);
 
         if (casepn->pn_right->pn_xflags & PNX_FUNCDEFS)
             list->pn_xflags |= PNX_FUNCDEFS;
     }
 
+    MOZ_MUST_USE inline bool addCatchBlock(ParseNode* catchList, ParseNode* lexicalScope,
+                              ParseNode* catchName, ParseNode* catchGuard,
+                              ParseNode* catchBody);
+
     MOZ_MUST_USE bool prependInitialYield(ParseNode* stmtList, ParseNode* genName) {
         MOZ_ASSERT(stmtList->isKind(PNK_STATEMENTLIST));
         MOZ_ASSERT(stmtList->isArity(PN_LIST));
 
         TokenPos yieldPos(stmtList->pn_pos.begin, stmtList->pn_pos.begin + 1);
         ParseNode* makeGen = new_<NullaryNode>(PNK_GENERATOR, yieldPos);
         if (!makeGen)
             return false;
 
         MOZ_ASSERT(genName->getOp() == JSOP_GETNAME);
         genName->setOp(JSOP_SETNAME);
-
-        ParseNode* genInit = newBinary(PNK_ASSIGN, genName, makeGen);
+        ParseNode* genInit = newAssignment(PNK_ASSIGN, /* lhs = */ genName, /* rhs = */ makeGen);
         if (!genInit)
             return false;
 
         ParseNode* initialYield = newInitialYieldExpression(yieldPos.begin, genInit);
         if (!initialYield)
             return false;
 
         stmtList->prepend(initialYield);
@@ -666,47 +675,61 @@ class FullParseHandler
     }
 
     bool isExpressionClosure(ParseNode* node) const {
         return node->isKind(PNK_FUNCTION) &&
                node->pn_funbox->isExprBody() &&
                !node->pn_funbox->isArrow();
     }
 
+    ParseNode* newObjectMethodOrPropertyDefinition(ParseNode* key, ParseNode* fn, AccessorType atype) {
+        MOZ_ASSERT(isUsableAsObjectPropertyName(key));
+
+        return newBinary(PNK_COLON, key, fn, AccessorTypeToJSOp(atype));
+    }
+
+    bool setComprehensionLambdaBody(ParseNode* pn, ParseNode* body) {
+        MOZ_ASSERT(body->isKind(PNK_STATEMENTLIST));
+        ParseNode* paramsBody = newList(PNK_PARAMSBODY, body);
+        if (!paramsBody)
+            return false;
+        setFunctionFormalParametersAndBody(pn, paramsBody);
+        return true;
+    }
     void setFunctionFormalParametersAndBody(ParseNode* funcNode, ParseNode* kid) {
         MOZ_ASSERT_IF(kid, kid->isKind(PNK_PARAMSBODY));
         funcNode->pn_body = kid;
     }
     void setFunctionBox(ParseNode* pn, FunctionBox* funbox) {
         MOZ_ASSERT(pn->isKind(PNK_FUNCTION));
         pn->pn_funbox = funbox;
         funbox->functionNode = pn;
     }
     void addFunctionFormalParameter(ParseNode* pn, ParseNode* argpn) {
-        pn->pn_body->append(argpn);
+        addList(/* list = */ pn->pn_body, /* child = */ argpn);
     }
     void setFunctionBody(ParseNode* fn, ParseNode* body) {
         MOZ_ASSERT(fn->pn_body->isKind(PNK_PARAMSBODY));
-        fn->pn_body->append(body);
+        addList(/* list = */ fn->pn_body, /* child = */ body);
     }
 
     ParseNode* newModule(const TokenPos& pos) {
         return new_<CodeNode>(PNK_MODULE, JSOP_NOP, pos);
     }
 
     ParseNode* newLexicalScope(LexicalScope::Data* bindings, ParseNode* body) {
         return new_<LexicalScopeNode>(bindings, body);
     }
 
     Node newNewExpression(uint32_t begin, ParseNode* ctor) {
         ParseNode* newExpr = new_<ListNode>(PNK_NEW, JSOP_NEW, TokenPos(begin, begin + 1));
         if (!newExpr)
             return nullptr;
 
-        addList(newExpr, ctor);
+        addList(/* list = */ newExpr, /* child = */ ctor);
         return newExpr;
     }
 
     ParseNode* newAssignment(ParseNodeKind kind, ParseNode* lhs, ParseNode* rhs) {
         return newBinary(kind, lhs, rhs);
     }
 
     bool isUnparenthesizedAssignment(Node node) {
@@ -739,16 +762,23 @@ class FullParseHandler
         return kind == PNK_FUNCTION || kind == PNK_VAR || kind == PNK_BREAK || kind == PNK_THROW ||
                (kind == PNK_SEMI && !node->pn_kid);
     }
 
     bool isSuperBase(ParseNode* node) {
         return node->isKind(PNK_SUPERBASE);
     }
 
+    bool isUsableAsObjectPropertyName(ParseNode* node) {
+        return node->isKind(PNK_NUMBER)
+            || node->isKind(PNK_OBJECT_PROPERTY_NAME)
+            || node->isKind(PNK_STRING)
+            || node->isKind(PNK_COMPUTED_NAME);
+    }
+
     inline MOZ_MUST_USE bool finishInitializerAssignment(ParseNode* pn, ParseNode* init);
 
     void setBeginPosition(ParseNode* pn, ParseNode* oth) {
         setBeginPosition(pn, oth->pn_pos.begin);
     }
     void setBeginPosition(ParseNode* pn, uint32_t begin) {
         pn->pn_pos.begin = begin;
         MOZ_ASSERT(pn->pn_pos.begin <= pn->pn_pos.end);
@@ -800,17 +830,20 @@ class FullParseHandler
         return decl->pn_head;
     }
 
     ParseNode* newCommaExpressionList(ParseNode* kid) {
         return new_<ListNode>(PNK_COMMA, JSOP_NOP, kid);
     }
 
     void addList(ParseNode* list, ParseNode* kid) {
-        list->append(kid);
+        if (sourceKind_ == SourceKind::Text)
+            list->append(kid);
+        else
+            list->appendWithoutOrderAssumption(kid);
     }
 
     void setOp(ParseNode* pn, JSOp op) {
         pn->setOp(op);
     }
     void setListFlag(ParseNode* pn, unsigned flag) {
         MOZ_ASSERT(pn->isArity(PN_LIST));
         pn->pn_xflags |= flag;
@@ -875,16 +908,30 @@ class FullParseHandler
     }
     JSAtom* nextLazyClosedOverBinding() {
         MOZ_ASSERT(lazyClosedOverBindingIndex < lazyOuterFunction_->numClosedOverBindings());
         return lazyOuterFunction_->closedOverBindings()[lazyClosedOverBindingIndex++];
     }
 };
 
 inline bool
+FullParseHandler::addCatchBlock(ParseNode* catchList, ParseNode* lexicalScope,
+                                ParseNode* catchName, ParseNode* catchGuard,
+                                ParseNode* catchBody)
+{
+    MOZ_ASSERT(catchList->isKind(PNK_CATCHLIST));
+    ParseNode* catchpn = newCatchBlock(catchName, catchGuard, catchBody);
+    if (!catchpn)
+        return false;
+    addList(/* list = */ catchList, /* child = */ lexicalScope);
+    lexicalScope->setScopeBody(catchpn);
+    return true;
+}
+
+inline bool
 FullParseHandler::setLastFunctionFormalParameterDefault(ParseNode* funcpn,
                                                         ParseNode* defaultValue)
 {
     MOZ_ASSERT(funcpn->isKind(PNK_FUNCTION));
     MOZ_ASSERT(funcpn->isArity(PN_CODE));
 
     ParseNode* arg = funcpn->pn_body->last();
     ParseNode* pn = newBinary(PNK_ASSIGN, arg, defaultValue);
new file mode 100644
--- /dev/null
+++ b/js/src/frontend/ParseContext-inl.h
@@ -0,0 +1,96 @@
+#ifndef frontend_ParseContext_inl_h
+#define frontend_ParseContext_inl_h
+
+#include "frontend/ParseContext.h"
+
+namespace js {
+namespace frontend {
+
+template <>
+inline bool
+ParseContext::Statement::is<ParseContext::LabelStatement>() const
+{
+    return kind_ == StatementKind::Label;
+}
+
+template <>
+inline bool
+ParseContext::Statement::is<ParseContext::ClassStatement>() const
+{
+    return kind_ == StatementKind::Class;
+}
+
+
+inline JS::Result<Ok, ParseContext::BreakStatementError>
+ParseContext::checkBreakStatement(PropertyName* label)
+{
+    // Labeled 'break' statements target the nearest labeled statements (could
+    // be any kind) with the same label. Unlabeled 'break' statements target
+    // the innermost loop or switch statement.
+    if (label) {
+        auto hasSameLabel = [&label](ParseContext::LabelStatement* stmt) {
+            MOZ_ASSERT(stmt);
+            return stmt->label() == label;
+        };
+
+        if (!findInnermostStatement<ParseContext::LabelStatement>(hasSameLabel))
+            return mozilla::Err(ParseContext::BreakStatementError::LabelNotFound);
+
+    } else {
+        auto isBreakTarget = [](ParseContext::Statement* stmt) {
+            return StatementKindIsUnlabeledBreakTarget(stmt->kind());
+        };
+
+        if (!findInnermostStatement(isBreakTarget))
+            return mozilla::Err(ParseContext::BreakStatementError::ToughBreak);
+    }
+
+    return Ok();
+}
+
+inline JS::Result<Ok, ParseContext::ContinueStatementError>
+ParseContext::checkContinueStatement(PropertyName* label)
+{
+    // Labeled 'continue' statements target the nearest labeled loop
+    // statements with the same label. Unlabeled 'continue' statements target
+    // the innermost loop statement.
+    auto isLoop = [](ParseContext::Statement* stmt) {
+        MOZ_ASSERT(stmt);
+        return StatementKindIsLoop(stmt->kind());
+    };
+
+    if (!label) {
+        // Unlabeled statement: we target the innermost loop, so make sure that
+        // there is an innermost loop.
+        if (!findInnermostStatement(isLoop))
+            return mozilla::Err(ParseContext::ContinueStatementError::NotInALoop);
+        return Ok();
+    }
+
+    // Labeled statement: targest the nearest labeled loop with the same label.
+    ParseContext::Statement* stmt = innermostStatement();
+    bool foundLoop = false; // True if we have encountered at least one loop.
+
+    for (;;) {
+        stmt = ParseContext::Statement::findNearest(stmt, isLoop);
+        if (!stmt)
+            return foundLoop ? mozilla::Err(ParseContext::ContinueStatementError::LabelNotFound)
+                             : mozilla::Err(ParseContext::ContinueStatementError::NotInALoop);
+
+        foundLoop = true;
+
+        // Is it labeled by our label?
+        stmt = stmt->enclosing();
+        while (stmt && stmt->is<ParseContext::LabelStatement>()) {
+            if (stmt->as<ParseContext::LabelStatement>().label() == label)
+                return Ok();
+
+            stmt = stmt->enclosing();
+        }
+    }
+}
+
+}
+}
+
+#endif // frontend_ParseContext_inl_h
--- a/js/src/frontend/ParseContext.h
+++ b/js/src/frontend/ParseContext.h
@@ -2,17 +2,21 @@
  * 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_ParseContext_h
 #define frontend_ParseContext_h
 
+#include "ds/Nestable.h"
+
+#include "frontend/BytecodeCompiler.h"
 #include "frontend/ErrorReporter.h"
+#include "frontend/SharedContext.h"
 
 namespace js {
 
 namespace frontend {
 
 class ParserBase;
 
 // A data structure for tracking used names per parsing session in order to
@@ -284,16 +288,20 @@ class ParseContext : public Nestable<Par
             if (id_ == UINT32_MAX) {
                 pc->errorReporter_.reportErrorNoOffset(JSMSG_NEED_DIET, js_script_str);
                 return false;
             }
 
             return declared_.acquire(pc->sc()->context);
         }
 
+        bool isEmpty() const {
+            return declared_->all().empty();
+        }
+
         DeclaredNamePtr lookupDeclaredName(JSAtom* name) {
             return declared_->lookup(name);
         }
 
         AddDeclaredNamePtr lookupDeclaredNameForAdd(JSAtom* name) {
             return declared_->lookupForAdd(name);
         }
 
@@ -397,16 +405,17 @@ class ParseContext : public Nestable<Par
 
         inline BindingIter bindings(ParseContext* pc);
     };
 
     class VarScope : public Scope
     {
       public:
         explicit inline VarScope(ParserBase* parser);
+        explicit inline VarScope(JSContext* cx, ParseContext* pc, UsedNameTracker& usedNames);
     };
 
   private:
     // Trace logging of parsing time.
     AutoFrontendTraceLog traceLog_;
 
     // Context shared between parsing and bytecode generation.
     SharedContext* sc_;
@@ -512,16 +521,17 @@ class ParseContext : public Nestable<Par
     }
 
     MOZ_MUST_USE bool init();
 
     SharedContext* sc() {
         return sc_;
     }
 
+    // `true` if we are in the body of a function definition.
     bool isFunctionBox() const {
         return sc_->isFunctionBox();
     }
 
     FunctionBox* functionBox() {
         return sc_->asFunctionBox();
     }
 
@@ -573,16 +583,32 @@ class ParseContext : public Nestable<Par
     AtomVector& positionalFormalParameterNames() {
         return *positionalFormalParameterNames_;
     }
 
     AtomVector& closedOverBindingsForLazy() {
         return *closedOverBindingsForLazy_;
     }
 
+    enum class BreakStatementError {
+        // Unlabeled break must be inside loop or switch.
+        ToughBreak,
+        LabelNotFound,
+    };
+
+    // Return Err(true) if we have encountered at least one loop,
+    // Err(false) otherwise.
+    MOZ_MUST_USE inline JS::Result<Ok, BreakStatementError> checkBreakStatement(PropertyName* label);
+
+    enum class ContinueStatementError {
+        NotInALoop,
+        LabelNotFound,
+    };
+    MOZ_MUST_USE inline JS::Result<Ok, ContinueStatementError> checkContinueStatement(PropertyName* label);
+
     // True if we are at the topmost level of a entire script or function body.
     // For example, while parsing this code we would encounter f1 and f2 at
     // body level, but we would not encounter f3 or f4 at body level:
     //
     //   function f1() { function f2() { } }
     //   if (cond) { function f3() { if (cond) { function f4() { } } } }
     //
     bool atBodyLevel() {
--- a/js/src/frontend/ParseNode.h
+++ b/js/src/frontend/ParseNode.h
@@ -8,16 +8,37 @@
 #define frontend_ParseNode_h
 
 #include "mozilla/Attributes.h"
 
 #include "builtin/ModuleObject.h"
 #include "frontend/TokenStream.h"
 #include "vm/Printer.h"
 
+// A few notes on lifetime of ParseNode trees:
+//
+// - All the `ParseNode` instances MUST BE explicitly allocated in the context's `LifoAlloc`.
+//   This is typically implemented by the `FullParseHandler` or it can be reimplemented with
+//   a custom `new_`.
+//
+// - The tree is bulk-deallocated when the parser is deallocated. Consequently, references
+//   to a subtree MUST NOT exist once the parser has been deallocated.
+//
+// - This bulk-deallocation DOES NOT run destructors.
+//
+// - Instances of `LexicalScope::Data` MUST BE allocated as instances of `ParseNode`, in the same
+//   `LifoAlloc`. They are bulk-deallocated alongside the rest of the tree.
+//
+// - Instances of `JSAtom` used throughout the tree (including instances of `PropertyName`) MUST
+//   be kept alive by the parser. This is done through an instance of `AutoKeepAtoms` held by
+//   the parser.
+//
+// - Once the parser is deallocated, the `JSAtom` instances MAY be garbage-collected.
+
+
 namespace js {
 namespace frontend {
 
 class ParseContext;
 class FullParseHandler;
 class FunctionBox;
 class ObjectBox;
 
@@ -593,16 +614,17 @@ class ParseNode
     /*
      * If |left| is a list of the given kind/left-associative op, append
      * |right| to it and return |left|.  Otherwise return a [left, right] list.
      */
     static ParseNode*
     appendOrCreateList(ParseNodeKind kind, ParseNode* left, ParseNode* right,
                        FullParseHandler* handler, ParseContext* pc);
 
+    // include "ParseNode-inl.h" for these methods.
     inline PropertyName* name() const;
     inline JSAtom* atom() const;
 
     ParseNode* expr() const {
         MOZ_ASSERT(pn_arity == PN_NAME || pn_arity == PN_CODE);
         return pn_expr;
     }
 
@@ -726,18 +748,22 @@ class ParseNode
         pn_pos.end = pn->pn_pos.end;
         pn_head = pn;
         pn_tail = &pn->pn_next;
         pn_count = 1;
         pn_xflags = 0;
     }
 
     void append(ParseNode* pn) {
+        MOZ_ASSERT(pn->pn_pos.begin >= pn_pos.begin);
+        appendWithoutOrderAssumption(pn);
+    }
+
+    void appendWithoutOrderAssumption(ParseNode* pn) {
         MOZ_ASSERT(pn_arity == PN_LIST);
-        MOZ_ASSERT(pn->pn_pos.begin >= pn_pos.begin);
         pn_pos.end = pn->pn_pos.end;
         *pn_tail = pn;
         pn_tail = &pn->pn_next;
         pn_count++;
     }
 
     void prepend(ParseNode* pn) {
         MOZ_ASSERT(pn_arity == PN_LIST);
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -38,16 +38,17 @@
 #include "frontend/TokenStream.h"
 #include "irregexp/RegExpParser.h"
 #include "vm/RegExpObject.h"
 #include "wasm/AsmJS.h"
 
 #include "jsatominlines.h"
 #include "jsscriptinlines.h"
 
+#include "frontend/ParseContext-inl.h"
 #include "frontend/ParseNode-inl.h"
 #include "vm/EnvironmentObject-inl.h"
 
 using namespace js;
 using namespace js::gc;
 
 using mozilla::Maybe;
 using mozilla::Move;
@@ -1714,32 +1715,33 @@ Parser<FullParseHandler, CharT>::checkSt
     if (tt != TOK_EOF) {
         error(JSMSG_UNEXPECTED_TOKEN, "expression", TokenKindToDesc(tt));
         return false;
     }
     return true;
 }
 
 template <typename Scope>
-static typename Scope::Data*
+typename Scope::Data*
 NewEmptyBindingData(JSContext* cx, LifoAlloc& alloc, uint32_t numBindings)
 {
     size_t allocSize = Scope::sizeOfData(numBindings);
     typename Scope::Data* bindings = static_cast<typename Scope::Data*>(alloc.alloc(allocSize));
     if (!bindings) {
         ReportOutOfMemory(cx);
         return nullptr;
     }
     PodZero(bindings);
     return bindings;
 }
 
 Maybe<GlobalScope::Data*>
-ParserBase::newGlobalScopeData(ParseContext::Scope& scope)
-{
+NewGlobalScopeData(JSContext* context, ParseContext::Scope& scope, LifoAlloc& alloc, ParseContext* pc)
+{
+
     Vector<BindingName> funs(context);
     Vector<BindingName> vars(context);
     Vector<BindingName> lets(context);
     Vector<BindingName> consts(context);
 
     bool allBindingsClosedOver = pc->sc()->allBindingsClosedOver();
     for (BindingIter bi = scope.bindings(pc); bi; bi++) {
         BindingName binding(bi.name(), allBindingsClosedOver || bi.closedOver());
@@ -1792,18 +1794,24 @@ ParserBase::newGlobalScopeData(ParseCont
         bindings->constStart = cursor - start;
         PodCopy(cursor, consts.begin(), consts.length());
         bindings->length = numBindings;
     }
 
     return Some(bindings);
 }
 
+Maybe<GlobalScope::Data*>
+ParserBase::newGlobalScopeData(ParseContext::Scope& scope)
+{
+    return NewGlobalScopeData(context, scope, alloc, pc);
+}
+
 Maybe<ModuleScope::Data*>
-ParserBase::newModuleScopeData(ParseContext::Scope& scope)
+NewModuleScopeData(JSContext* context, ParseContext::Scope& scope, LifoAlloc& alloc, ParseContext* pc)
 {
     Vector<BindingName> imports(context);
     Vector<BindingName> vars(context);
     Vector<BindingName> lets(context);
     Vector<BindingName> consts(context);
 
     bool allBindingsClosedOver = pc->sc()->allBindingsClosedOver();
     for (BindingIter bi = scope.bindings(pc); bi; bi++) {
@@ -1858,18 +1866,24 @@ ParserBase::newModuleScopeData(ParseCont
         bindings->constStart = cursor - start;
         PodCopy(cursor, consts.begin(), consts.length());
         bindings->length = numBindings;
     }
 
     return Some(bindings);
 }
 
+Maybe<ModuleScope::Data*>
+ParserBase::newModuleScopeData(ParseContext::Scope& scope)
+{
+    return NewModuleScopeData(context, scope, alloc, pc);
+}
+
 Maybe<EvalScope::Data*>
-ParserBase::newEvalScopeData(ParseContext::Scope& scope)
+NewEvalScopeData(JSContext* context, ParseContext::Scope& scope, LifoAlloc& alloc, ParseContext* pc)
 {
     Vector<BindingName> funs(context);
     Vector<BindingName> vars(context);
 
     for (BindingIter bi = scope.bindings(pc); bi; bi++) {
         // Eval scopes only contain 'var' bindings. Make all bindings aliased
         // for now.
         MOZ_ASSERT(bi.kind() == BindingKind::Var);
@@ -1902,18 +1916,24 @@ ParserBase::newEvalScopeData(ParseContex
         bindings->varStart = cursor - start;
         PodCopy(cursor, vars.begin(), vars.length());
         bindings->length = numBindings;
     }
 
     return Some(bindings);
 }
 
+Maybe<EvalScope::Data*>
+ParserBase::newEvalScopeData(ParseContext::Scope& scope)
+{
+    return NewEvalScopeData(context, scope, alloc, pc);
+}
+
 Maybe<FunctionScope::Data*>
-ParserBase::newFunctionScopeData(ParseContext::Scope& scope, bool hasParameterExprs)
+NewFunctionScopeData(JSContext* context, ParseContext::Scope& scope, bool hasParameterExprs, LifoAlloc& alloc, ParseContext* pc)
 {
     Vector<BindingName> positionalFormals(context);
     Vector<BindingName> formals(context);
     Vector<BindingName> vars(context);
 
     bool allBindingsClosedOver = pc->sc()->allBindingsClosedOver();
     bool hasDuplicateParams = pc->functionBox()->hasDuplicateParameters;
 
@@ -1996,18 +2016,24 @@ ParserBase::newFunctionScopeData(ParseCo
         bindings->varStart = cursor - start;
         PodCopy(cursor, vars.begin(), vars.length());
         bindings->length = numBindings;
     }
 
     return Some(bindings);
 }
 
+Maybe<FunctionScope::Data*>
+ParserBase::newFunctionScopeData(ParseContext::Scope& scope, bool hasParameterExprs)
+{
+    return NewFunctionScopeData(context, scope, hasParameterExprs, alloc, pc);
+}
+
 Maybe<VarScope::Data*>
-ParserBase::newVarScopeData(ParseContext::Scope& scope)
+NewVarScopeData(JSContext* context, ParseContext::Scope& scope, LifoAlloc& alloc, ParseContext* pc)
 {
     Vector<BindingName> vars(context);
 
     bool allBindingsClosedOver = pc->sc()->allBindingsClosedOver();
 
     for (BindingIter bi = scope.bindings(pc); bi; bi++) {
         BindingName binding(bi.name(), allBindingsClosedOver || bi.closedOver());
         if (!vars.append(binding))
@@ -2028,18 +2054,24 @@ ParserBase::newVarScopeData(ParseContext
 
         PodCopy(cursor, vars.begin(), vars.length());
         bindings->length = numBindings;
     }
 
     return Some(bindings);
 }
 
+Maybe<VarScope::Data*>
+ParserBase::newVarScopeData(ParseContext::Scope& scope)
+{
+    return NewVarScopeData(context, scope, alloc, pc);
+}
+
 Maybe<LexicalScope::Data*>
-ParserBase::newLexicalScopeData(ParseContext::Scope& scope)
+NewLexicalScopeData(JSContext* context, ParseContext::Scope& scope, LifoAlloc& alloc, ParseContext* pc)
 {
     Vector<BindingName> lets(context);
     Vector<BindingName> consts(context);
 
     // Unlike other scopes with bindings which are body-level, it is unknown
     // if pc->sc()->allBindingsClosedOver() is correct at the time of
     // finishing parsing a lexical scope.
     //
@@ -2079,16 +2111,22 @@ ParserBase::newLexicalScopeData(ParseCon
         bindings->constStart = cursor - start;
         PodCopy(cursor, consts.begin(), consts.length());
         bindings->length = numBindings;
     }
 
     return Some(bindings);
 }
 
+Maybe<LexicalScope::Data*>
+ParserBase::newLexicalScopeData(ParseContext::Scope& scope)
+{
+    return NewLexicalScopeData(context, scope, alloc, pc);
+}
+
 template <typename CharT>
 SyntaxParseHandler::Node
 Parser<SyntaxParseHandler, CharT>::finishLexicalScope(ParseContext::Scope& scope, Node body)
 {
     if (!propagateFreeNamesAndMarkClosedOverBindings(scope))
         return null();
 
     return body;
@@ -6543,54 +6581,26 @@ GeneralParser<ParseHandler, CharT>::cont
 {
     MOZ_ASSERT(anyChars.isCurrentTokenType(TOK_CONTINUE));
     uint32_t begin = pos().begin;
 
     RootedPropertyName label(context);
     if (!matchLabel(yieldHandling, &label))
         return null();
 
-    // Labeled 'continue' statements target the nearest labeled loop
-    // statements with the same label. Unlabeled 'continue' statements target
-    // the innermost loop statement.
-    auto isLoop = [](ParseContext::Statement* stmt) {
-        return StatementKindIsLoop(stmt->kind());
-    };
-
-    if (label) {
-        ParseContext::Statement* stmt = pc->innermostStatement();
-        bool foundLoop = false;
-
-        for (;;) {
-            stmt = ParseContext::Statement::findNearest(stmt, isLoop);
-            if (!stmt) {
-                if (foundLoop)
-                    error(JSMSG_LABEL_NOT_FOUND);
-                else
-                    errorAt(begin, JSMSG_BAD_CONTINUE);
-                return null();
-            }
-
-            foundLoop = true;
-
-            // Is it labeled by our label?
-            bool foundTarget = false;
-            stmt = stmt->enclosing();
-            while (stmt && stmt->is<ParseContext::LabelStatement>()) {
-                if (stmt->as<ParseContext::LabelStatement>().label() == label) {
-                    foundTarget = true;
-                    break;
-                }
-                stmt = stmt->enclosing();
-            }
-            if (foundTarget)
-                break;
-        }
-    } else if (!pc->findInnermostStatement(isLoop)) {
-        error(JSMSG_BAD_CONTINUE);
+    auto validity = pc->checkContinueStatement(label);
+    if (validity.isErr()) {
+        switch (validity.unwrapErr()) {
+          case ParseContext::ContinueStatementError::NotInALoop:
+            errorAt(begin, JSMSG_BAD_CONTINUE);
+            break;
+          case ParseContext::ContinueStatementError::LabelNotFound:
+            error(JSMSG_LABEL_NOT_FOUND);
+            break;
+        }
         return null();
     }
 
     if (!matchOrInsertSemicolon())
         return null();
 
     return handler.newContinueStatement(label, TokenPos(begin, pos().end));
 }
--- a/js/src/frontend/Parser.h
+++ b/js/src/frontend/Parser.h
@@ -44,30 +44,16 @@ public:
     template<typename ParseHandler, typename CharT>
     SourceParseContext(GeneralParser<ParseHandler, CharT>* prs, SharedContext* sc,
                        Directives* newDirectives)
       : ParseContext(prs->context, prs->pc, sc, prs->anyChars, prs->usedNames, newDirectives,
                      mozilla::IsSame<ParseHandler, FullParseHandler>::value)
     { }
 };
 
-template <>
-inline bool
-ParseContext::Statement::is<ParseContext::LabelStatement>() const
-{
-    return kind_ == StatementKind::Label;
-}
-
-template <>
-inline bool
-ParseContext::Statement::is<ParseContext::ClassStatement>() const
-{
-    return kind_ == StatementKind::Class;
-}
-
 template <typename T>
 inline T&
 ParseContext::Statement::as()
 {
     MOZ_ASSERT(is<T>());
     return static_cast<T&>(*this);
 }
 
@@ -266,16 +252,23 @@ ParseContext::Scope::Scope(JSContext* cx
 
 inline
 ParseContext::VarScope::VarScope(ParserBase* parser)
   : Scope(parser)
 {
     useAsVarScope(parser->pc);
 }
 
+inline
+ParseContext::VarScope::VarScope(JSContext* cx, ParseContext* pc, UsedNameTracker& usedNames)
+  : Scope(cx, pc, usedNames)
+{
+    useAsVarScope(pc);
+}
+
 enum class ExpressionClosure { Allowed, Forbidden };
 
 template<class Parser>
 class ParserAnyCharsAccess
 {
   public:
     using TokenStreamSpecific = typename Parser::TokenStream;
     using TokenStreamChars = typename TokenStreamSpecific::CharsBase;
@@ -1284,12 +1277,27 @@ class MOZ_STACK_CLASS AutoAwaitIsKeyword
             parser_->setAwaitHandling(awaitHandling);
     }
 
     ~AutoAwaitIsKeyword() {
         parser_->setAwaitHandling(oldAwaitHandling_);
     }
 };
 
+template <typename Scope>
+extern typename Scope::Data*
+NewEmptyBindingData(JSContext* cx, LifoAlloc& alloc, uint32_t numBindings);
+
+Maybe<GlobalScope::Data*>
+NewGlobalScopeData(JSContext* context, ParseContext::Scope& scope, LifoAlloc& alloc, ParseContext* pc);
+Maybe<EvalScope::Data*>
+NewEvalScopeData(JSContext* context, ParseContext::Scope& scope, LifoAlloc& alloc, ParseContext* pc);
+Maybe<FunctionScope::Data*>
+NewFunctionScopeData(JSContext* context, ParseContext::Scope& scope, bool hasParameterExprs, LifoAlloc& alloc, ParseContext* pc);
+Maybe<VarScope::Data*>
+NewVarScopeData(JSContext* context, ParseContext::Scope& scope, LifoAlloc& alloc, ParseContext* pc);
+Maybe<LexicalScope::Data*>
+NewLexicalScopeData(JSContext* context, ParseContext::Scope& scope, LifoAlloc& alloc, ParseContext* pc);
+
 } /* namespace frontend */
 } /* namespace js */
 
 #endif /* frontend_Parser_h */
--- a/js/src/frontend/SharedContext.h
+++ b/js/src/frontend/SharedContext.h
@@ -10,22 +10,24 @@
 #include "jsatom.h"
 #include "jsopcode.h"
 #include "jspubtd.h"
 #include "jsscript.h"
 #include "jstypes.h"
 
 #include "builtin/ModuleObject.h"
 #include "ds/InlineTable.h"
+#include "frontend/ParseNode.h"
 #include "frontend/TokenStream.h"
 #include "vm/EnvironmentObject.h"
 
 namespace js {
 namespace frontend {
 
+class ParseContext;
 class ParseNode;
 
 enum class StatementKind : uint8_t
 {
     Label,
     Block,
     If,
     Switch,
--- a/js/src/jsapi-tests/testBinASTReader.cpp
+++ b/js/src/jsapi-tests/testBinASTReader.cpp
@@ -3,69 +3,97 @@
  */
 /* 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/. */
 
 
 #include "mozilla/Vector.h"
 
+#if defined(XP_UNIX)
+
 #include <dirent.h>
 #include <sys/stat.h>
 
+#elif defined(XP_WIN)
+
+#include <windows.h>
+
+#endif
+
 #include "frontend/BinSource.h"
 #include "frontend/FullParseHandler.h"
 #include "frontend/ParseContext.h"
 #include "frontend/Parser.h"
 
 #include "jsapi-tests/tests.h"
 
 using UsedNameTracker = js::frontend::UsedNameTracker;
 
-extern void readFull(const char* path, Vector<char>& buf);
+extern void readFull(const char* path, Vector<uint8_t>& buf);
 
 void readFull(JSContext* cx, const char* path, Vector<char16_t>& buf) {
     buf.shrinkTo(0);
 
-    Vector<char> intermediate(cx);
+    Vector<uint8_t> intermediate(cx);
     readFull(path, intermediate);
 
     if (!buf.appendAll(intermediate))
         MOZ_CRASH();
 }
 
 BEGIN_TEST(testBinASTReaderECMAScript2)
 {
-    const char PATH[] = "jsapi-tests/binast/parser/tester/";
-
-    // Read the list of files in the directory.
-    DIR* dir = opendir(PATH);
-    if (!dir)
-        MOZ_CRASH();
-
     const char BIN_SUFFIX[] = ".binjs";
     const char TXT_SUFFIX[] = ".js";
 
     CompileOptions options(cx);
     options.setIntroductionType("unit test parse")
            .setFileAndLine("<string>", 1);
 
+#if defined(XP_UNIX)
+
+    const char PATH[] = "jsapi-tests/binast/parser/tester/";
+
+    // Read the list of files in the directory.
+    DIR* dir = opendir(PATH);
+    if (!dir)
+        MOZ_CRASH();
+
+
     while (auto entry = readdir(dir)) {
         // Find files whose name ends with ".binjs".
-        if (entry->d_namlen < sizeof(BIN_SUFFIX))
+        const char* d_name = entry->d_name;
+
+#elif defined(XP_WIN)
+
+    const char PATH[] = "jsapi-tests\\binast\\parser\\tester\\*.binjs";
+
+    WIN32_FIND_DATA FindFileData;
+    HANDLE hFind = FindFirstFile(PATH, &FindFileData);
+    for (bool found = (hFind != INVALID_HANDLE_VALUE);
+            found;
+            found = FindNextFile(hFind, &FindFileData)
+    {
+        const char* d_name = FindFileData.cFileName;
+
+#endif // defined(XP_UNIX) || defined(XP_WIN)
+
+        const size_t namlen = strlen(d_name);
+        if (namlen < sizeof(BIN_SUFFIX))
             continue;
-        if (strncmp(entry->d_name + entry->d_namlen - (sizeof(BIN_SUFFIX) - 1), BIN_SUFFIX, sizeof(BIN_SUFFIX)) != 0)
+        if (strncmp(d_name + namlen - (sizeof(BIN_SUFFIX) - 1), BIN_SUFFIX, sizeof(BIN_SUFFIX)) != 0)
             continue;
 
         // Find text file.
-        UniqueChars txtPath(static_cast<char*>(js_malloc(entry->d_namlen + sizeof(PATH) + 1)));
+        UniqueChars txtPath(static_cast<char*>(js_malloc(namlen + sizeof(PATH) + 1)));
         strncpy(txtPath.get(), PATH, sizeof(PATH));
-        strncpy(txtPath.get() + sizeof(PATH) - 1, entry->d_name, entry->d_namlen);
-        strncpy(txtPath.get() + sizeof(PATH) + entry->d_namlen - sizeof(BIN_SUFFIX), TXT_SUFFIX, sizeof(TXT_SUFFIX));
-        txtPath[sizeof(PATH) + entry->d_namlen - sizeof(BIN_SUFFIX) + sizeof(TXT_SUFFIX) - 1] = 0;
+        strncpy(txtPath.get() + sizeof(PATH) - 1, d_name, namlen);
+        strncpy(txtPath.get() + sizeof(PATH) + namlen - sizeof(BIN_SUFFIX), TXT_SUFFIX, sizeof(TXT_SUFFIX));
+        txtPath[sizeof(PATH) + namlen - sizeof(BIN_SUFFIX) + sizeof(TXT_SUFFIX) - 1] = 0;
         fprintf(stderr, "Testing %s\n", txtPath.get());
 
         // Read text file.
         Vector<char16_t> txtSource(cx);
         readFull(cx, txtPath.get(), txtSource);
 
         // Parse text file.
         UsedNameTracker txtUsedNames(cx);
@@ -81,22 +109,22 @@ BEGIN_TEST(testBinASTReaderECMAScript2)
         RootedValue txtExn(cx);
         if (!txtParsed) {
             // Save exception for more detailed error message, if necessary.
             if (!js::GetAndClearException(cx, &txtExn))
                 MOZ_CRASH();
         }
 
         // Read binary file.
-        UniqueChars binPath(static_cast<char*>(js_malloc(entry->d_namlen + sizeof(PATH) + 1)));
+        UniqueChars binPath(static_cast<char*>(js_malloc(namlen + sizeof(PATH) + 1)));
         strncpy(binPath.get(), PATH, sizeof(PATH));
-        strncpy(binPath.get() + sizeof(PATH) - 1, entry->d_name, entry->d_namlen);
-        binPath[entry->d_namlen + sizeof(PATH) - 1] = 0;
+        strncpy(binPath.get() + sizeof(PATH) - 1, d_name, namlen);
+        binPath[namlen + sizeof(PATH) - 1] = 0;
 
-        Vector<char> binSource(cx);
+        Vector<uint8_t> binSource(cx);
         readFull(binPath.get(), binSource);
 
         // Parse binary file.
         js::frontend::UsedNameTracker binUsedNames(cx);
         if (!binUsedNames.init())
             MOZ_CRASH();
 
         js::frontend::BinASTParser reader(cx, cx->tempLifoAlloc(), binUsedNames, options);
@@ -153,12 +181,17 @@ BEGIN_TEST(testBinASTReaderECMAScript2)
             fprintf(stderr, "Got distinct ASTs when parsing %s:\n\tBINARY\n%s\n\n\tTEXT\n%s\n", txtPath.get(), binPrinter.string(), txtPrinter.string());            
             MOZ_CRASH();
         }
         fprintf(stderr, "Got the same AST when parsing %s\n", txtPath.get());
 
 #endif // defined(DEBUG)
     }
 
+#if defined(XP_WIN)
+    if (!FindClose(hFind))
+        MOZ_CRASH("Could not close Find");
+#endif // defined(XP_WIN)
+
     return true;
 }
 END_TEST(testBinASTReaderECMAScript2)
 
--- a/js/src/moz.build
+++ b/js/src/moz.build
@@ -642,17 +642,20 @@ if CONFIG['NIGHTLY_BUILD']:
     DEFINES['ENABLE_SIMD'] = True
     DEFINES['ENABLE_WASM_THREAD_OPS'] = True
 
 if CONFIG['JS_BUILD_BINAST']:
     # Some parts of BinAST are designed only to test evolutions of the
     # specification:
     UNIFIED_SOURCES += ['frontend/BinTokenReaderTester.cpp']
     # The rest of BinAST should eventually move to release.
-    UNIFIED_SOURCES += ['frontend/BinToken.cpp']
+    UNIFIED_SOURCES += [
+        'frontend/BinSource.cpp',
+        'frontend/BinToken.cpp'
+    ]
 
 # Wasm code should use WASM_HUGE_MEMORY instead of JS_CODEGEN_X64
 # so that it is easy to use the huge-mapping optimization for other
 # 64-bit platforms in the future.
 
 if CONFIG['JS_CODEGEN_X64']:
     DEFINES['WASM_HUGE_MEMORY'] = True