Bug 1377007 - Implementing basic binjs-ref parser in SpiderMonkey;r=arai,jorendorff
☠☠ backed out by d0d709880f19 ☠ ☠
authorDavid Teller <dteller@mozilla.com>
Mon, 11 Sep 2017 16:54:48 +0200
changeset 396492 e9310960c9e6b32cac8517afdd4df33649fb602f
parent 396491 8e5e61dfbbaf45cc6bf0f364b68c5aad99958de8
child 396493 4a452c3ac1156d2a69d325995057cf5c1c736151
push id98324
push usercbrindusan@mozilla.com
push dateFri, 15 Dec 2017 21:58:41 +0000
treeherdermozilla-inbound@e4d571577ef1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersarai, jorendorff
bugs1377007
milestone59.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1377007 - Implementing basic binjs-ref parser in SpiderMonkey;r=arai,jorendorff 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,2503 @@
+#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(bindings, NewLexicalScopeData(cx_, scope, alloc_, parseContext_));
+        TRY_VAR(result, factory_.newLexicalScope(*bindings, body));
+        TRY(factory_.setupCatchScope(result, param, body));
+      }
+    }
+
+    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,29 @@ 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)
+{
+    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