/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- * vim: set ts=8 sts=2 et sw=2 tw=80: * 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 "frontend/BinASTParserPerTokenizer.h" #include "mozilla/ArrayUtils.h" #include "mozilla/Casting.h" #include "mozilla/Maybe.h" #include "mozilla/Move.h" #include "mozilla/PodOperations.h" #include "mozilla/ScopeExit.h" #include "mozilla/Vector.h" #include "frontend/BinAST-macros.h" #include "frontend/BinASTParser.h" #include "frontend/BinASTTokenReaderContext.h" #include "frontend/BinASTTokenReaderMultipart.h" #include "frontend/FullParseHandler.h" #include "frontend/ParseNode.h" #include "frontend/Parser.h" #include "frontend/SharedContext.h" #include "js/Result.h" #include "vm/RegExpObject.h" #include "frontend/ParseContext-inl.h" #include "frontend/SharedContext-inl.h" #include "vm/JSContext-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. namespace js { namespace frontend { using UsedNamePtr = UsedNameTracker::UsedNameMap::Ptr; // ------------- Toplevel constructions template BinASTParserPerTokenizer::BinASTParserPerTokenizer( JSContext* cx, LifoAlloc& alloc, UsedNameTracker& usedNames, const JS::ReadOnlyCompileOptions& options, HandleScriptSourceObject sourceObject, Handle lazyScript /* = nullptr */) : BinASTParserBase(cx, alloc, usedNames, sourceObject), options_(options), lazyScript_(cx, lazyScript), handler_(cx, alloc, nullptr, SourceKind::Binary), variableDeclarationKind_(VariableDeclarationKind::Var) { MOZ_ASSERT_IF(lazyScript_, lazyScript_->isBinAST()); } template JS::Result BinASTParserPerTokenizer::parse( GlobalSharedContext* globalsc, const Vector& data, BinASTSourceMetadata** metadataPtr) { return parse(globalsc, data.begin(), data.length(), metadataPtr); } template JS::Result BinASTParserPerTokenizer::parse( GlobalSharedContext* globalsc, const uint8_t* start, const size_t length, BinASTSourceMetadata** metadataPtr) { auto result = parseAux(globalsc, start, length, metadataPtr); poison(); // Make sure that the parser is never used again accidentally. return result; } template JS::Result BinASTParserPerTokenizer::parseAux( GlobalSharedContext* globalsc, const uint8_t* start, const size_t length, BinASTSourceMetadata** metadataPtr) { MOZ_ASSERT(globalsc); tokenizer_.emplace(cx_, this, start, length); BinASTParseContext 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(); } MOZ_TRY(tokenizer_->readHeader()); ParseNode* result(nullptr); const Context topContext(Context::topLevel()); MOZ_TRY_VAR(result, asFinalParser()->parseProgram(topContext)); mozilla::Maybe bindings = NewGlobalScopeData(cx_, varScope, alloc_, pc_); if (!bindings) { return cx_->alreadyReportedError(); } globalsc->bindings = *bindings; if (metadataPtr) { *metadataPtr = tokenizer_->takeMetadata(); } return result; // Magic conversion to Ok. } template JS::Result BinASTParserPerTokenizer::parseLazyFunction( ScriptSource* scriptSource, const size_t firstOffset) { MOZ_ASSERT(lazyScript_); MOZ_ASSERT(scriptSource->length() > firstOffset); tokenizer_.emplace(cx_, this, scriptSource->binASTSource(), scriptSource->length()); MOZ_TRY(tokenizer_->initFromScriptSource(scriptSource)); tokenizer_->seek(firstOffset); // For now, only function declarations and function expression are supported. RootedFunction func(cx_, lazyScript_->functionNonDelazifying()); bool isExpr = func->isLambda(); MOZ_ASSERT(func->kind() == JSFunction::FunctionKind::NormalFunction); // Poison the tokenizer when we leave to ensure that it's not used again by // accident. auto onExit = mozilla::MakeScopeExit([&]() { poison(); }); // TODO: This should be actually shared with the auto-generated version. auto syntaxKind = isExpr ? FunctionSyntaxKind::Expression : FunctionSyntaxKind::Statement; BINJS_MOZ_TRY_DECL( funbox, buildFunctionBox(lazyScript_->generatorKind(), lazyScript_->asyncKind(), syntaxKind, nullptr)); // Push a new ParseContext. It will be used to parse `scope`, the arguments, // the function. BinASTParseContext funpc(cx_, this, funbox, /* newDirectives = */ nullptr); BINJS_TRY(funpc.init()); pc_->functionScope().useAsVarScope(pc_); MOZ_ASSERT(pc_->isFunctionBox()); ParseContext::Scope lexicalScope(cx_, pc_, usedNames_); BINJS_TRY(lexicalScope.init(pc_)); ListNode* params; ListNode* tmpBody; auto parseFunc = isExpr ? &FinalParser::parseFunctionExpressionContents : &FinalParser::parseFunctionOrMethodContents; // Inject a toplevel context (i.e. no parent) to parse the lazy content. // In the future, we may move this to a more specific context. const Context context(Context::topLevel()); MOZ_TRY( (asFinalParser()->*parseFunc)(func->nargs(), ¶ms, &tmpBody, context)); BINJS_TRY_DECL(lexicalScopeData, NewLexicalScopeData(cx_, lexicalScope, alloc_, pc_)); BINJS_TRY_DECL(body, handler_.newLexicalScope(*lexicalScopeData, tmpBody)); auto binASTKind = isExpr ? BinASTKind::LazyFunctionExpression : BinASTKind::LazyFunctionDeclaration; return buildFunction(firstOffset, binASTKind, nullptr, params, body); } template void BinASTParserPerTokenizer::forceStrictIfNecessary( SharedContext* sc, ListNode* directives) { JSAtom* useStrict = cx_->names().useStrict; for (const ParseNode* directive : directives->contents()) { if (directive->as().atom() == useStrict) { sc->strictScript = true; break; } } } template JS::Result BinASTParserPerTokenizer::buildFunctionBox( GeneratorKind generatorKind, FunctionAsyncKind functionAsyncKind, FunctionSyntaxKind syntax, ParseNode* name) { MOZ_ASSERT_IF(!pc_, lazyScript_); RootedAtom atom(cx_); // Name might be any of {Identifier,ComputedPropertyName,LiteralPropertyName} if (name && name->is()) { atom = name->as().atom(); } if (pc_ && syntax == FunctionSyntaxKind::Statement) { auto ptr = pc_->varScope().lookupDeclaredName(atom); if (!ptr) { return raiseError( "FunctionDeclaration without corresponding AssertedDeclaredName."); } DeclarationKind declaredKind = ptr->value()->kind(); if (DeclarationKindIsVar(declaredKind)) { if (!pc_->atBodyLevel()) { return raiseError( "body-level FunctionDeclaration inside non-body-level context."); } RedeclareVar(ptr, DeclarationKind::BodyLevelFunction); } } // Allocate the function before walking down the tree. RootedFunction fun(cx_); BINJS_TRY_VAR(fun, !pc_ ? lazyScript_->functionNonDelazifying() : AllocNewFunction(cx_, atom, syntax, generatorKind, functionAsyncKind, nullptr)); MOZ_ASSERT_IF(pc_, fun->explicitName() == atom); mozilla::Maybe directives; if (pc_) { directives.emplace(pc_); } else { directives.emplace(lazyScript_->strict()); } auto* funbox = alloc_.new_( cx_, traceListHead_, fun, /* toStringStart = */ 0, *directives, /* extraWarning = */ false, generatorKind, functionAsyncKind); if (!funbox) { return raiseOOM(); } traceListHead_ = funbox; if (pc_) { funbox->initWithEnclosingParseContext(pc_, fun,syntax); } else { funbox->initFromLazyFunction(fun); } return funbox; } FunctionSyntaxKind BinASTKindToFunctionSyntaxKind(const BinASTKind kind) { // FIXME: this doesn't cover FunctionSyntaxKind::ClassConstructor and // FunctionSyntaxKind::DerivedClassConstructor. switch (kind) { case BinASTKind::EagerFunctionDeclaration: case BinASTKind::LazyFunctionDeclaration: return FunctionSyntaxKind::Statement; case BinASTKind::EagerFunctionExpression: case BinASTKind::LazyFunctionExpression: return FunctionSyntaxKind::Expression; case BinASTKind::EagerArrowExpressionWithFunctionBody: case BinASTKind::LazyArrowExpressionWithFunctionBody: case BinASTKind::EagerArrowExpressionWithExpression: case BinASTKind::LazyArrowExpressionWithExpression: return FunctionSyntaxKind::Arrow; case BinASTKind::EagerMethod: case BinASTKind::LazyMethod: return FunctionSyntaxKind::Method; case BinASTKind::EagerGetter: case BinASTKind::LazyGetter: return FunctionSyntaxKind::Getter; case BinASTKind::EagerSetter: case BinASTKind::LazySetter: return FunctionSyntaxKind::Setter; default: MOZ_CRASH("Invalid/ kind"); } } template JS::Result BinASTParserPerTokenizer::makeEmptyFunctionNode( const size_t start, const BinASTKind kind, FunctionBox* funbox) { // LazyScript compilation requires basically none of the fields filled out. TokenPos pos = tokenizer_->pos(start); FunctionSyntaxKind syntaxKind = BinASTKindToFunctionSyntaxKind(kind); BINJS_TRY_DECL(result, handler_.newFunction(syntaxKind, pos)); handler_.setFunctionBox(result, funbox); return result; } template JS::Result BinASTParserPerTokenizer::buildFunction( const size_t start, const BinASTKind kind, ParseNode* name, ListNode* params, ParseNode* body) { FunctionBox* funbox = pc_->functionBox(); // Set the argument count for building argument packets. Function.length is // handled by setting the appropriate funbox field during argument parsing. if (!lazyScript_ || lazyScript_->functionNonDelazifying() != funbox->function()) { funbox->function()->setArgCount(params ? uint16_t(params->count()) : 0); } // ParseNode represents the body as concatenated after the params. params->appendWithoutOrderAssumption(body); BINJS_MOZ_TRY_DECL(result, makeEmptyFunctionNode(start, kind, funbox)); handler_.setFunctionFormalParametersAndBody(result, params); if (funbox->needsDotGeneratorName()) { BINJS_TRY(pc_->declareDotGeneratorName()); HandlePropertyName dotGenerator = cx_->names().dotGenerator; BINJS_TRY(usedNames_.noteUse(cx_, dotGenerator, pc_->scriptId(), pc_->innermostScope()->id())); if (funbox->isGenerator()) { BINJS_TRY_DECL( dotGen, handler_.newName(dotGenerator, tokenizer_->pos(tokenizer_->offset()), cx_)); ListNode* stmtList = &body->as().scopeBody()->as(); BINJS_TRY(handler_.prependInitialYield(stmtList, dotGen)); } } const bool canSkipLazyClosedOverBindings = false; BINJS_TRY(pc_->declareFunctionArgumentsObject(usedNames_, canSkipLazyClosedOverBindings)); BINJS_TRY( pc_->declareFunctionThis(usedNames_, canSkipLazyClosedOverBindings)); // Check all our bindings after maybe adding function metavars. MOZ_TRY(checkFunctionClosedVars()); BINJS_TRY_DECL(bindings, NewFunctionScopeData(cx_, pc_->functionScope(), /* hasParameterExprs = */ false, IsFieldInitializer::No, alloc_, pc_)); funbox->functionScopeBindings().set(*bindings); if (funbox->isNamedLambda()) { BINJS_TRY_DECL( recursiveBinding, NewLexicalScopeData(cx_, pc_->namedLambdaScope(), alloc_, pc_)); funbox->namedLambdaBindings().set(*recursiveBinding); } return result; } template JS::Result BinASTParserPerTokenizer::addScopeName( AssertedScopeKind scopeKind, HandleAtom name, ParseContext::Scope* scope, DeclarationKind declKind, bool isCaptured, bool allowDuplicateName) { auto ptr = scope->lookupDeclaredNameForAdd(name); if (ptr) { if (allowDuplicateName) { return Ok(); } return raiseError("Variable redeclaration"); } BINJS_TRY(scope->addDeclaredName(pc_, ptr, name.get(), declKind, tokenizer_->offset())); if (isCaptured) { auto declaredPtr = scope->lookupDeclaredName(name); MOZ_ASSERT(declaredPtr); declaredPtr->value()->setClosedOver(); } return Ok(); } template void BinASTParserPerTokenizer::captureFunctionName() { MOZ_ASSERT(pc_->isFunctionBox()); MOZ_ASSERT(pc_->functionBox()->isNamedLambda()); RootedAtom funName(cx_, pc_->functionBox()->explicitName()); MOZ_ASSERT(funName); auto ptr = pc_->namedLambdaScope().lookupDeclaredName(funName); MOZ_ASSERT(ptr); ptr->value()->setClosedOver(); } template JS::Result BinASTParserPerTokenizer::getDeclaredScope( AssertedScopeKind scopeKind, AssertedDeclaredKind kind, ParseContext::Scope*& scope, DeclarationKind& declKind) { MOZ_ASSERT(scopeKind == AssertedScopeKind::Block || scopeKind == AssertedScopeKind::Global || scopeKind == AssertedScopeKind::Var); switch (kind) { case AssertedDeclaredKind::Var: if (scopeKind == AssertedScopeKind::Block) { return raiseError("AssertedBlockScope cannot contain 'var' binding"); } declKind = DeclarationKind::Var; scope = &pc_->varScope(); break; case AssertedDeclaredKind::NonConstLexical: declKind = DeclarationKind::Let; scope = pc_->innermostScope(); break; case AssertedDeclaredKind::ConstLexical: declKind = DeclarationKind::Const; scope = pc_->innermostScope(); break; } return Ok(); } template JS::Result BinASTParserPerTokenizer::getBoundScope( AssertedScopeKind scopeKind, ParseContext::Scope*& scope, DeclarationKind& declKind) { MOZ_ASSERT(scopeKind == AssertedScopeKind::Catch || scopeKind == AssertedScopeKind::Parameter); switch (scopeKind) { case AssertedScopeKind::Catch: declKind = DeclarationKind::CatchParameter; scope = pc_->innermostScope(); break; case AssertedScopeKind::Parameter: MOZ_ASSERT(pc_->isFunctionBox()); declKind = DeclarationKind::PositionalFormalParameter; scope = &pc_->functionScope(); break; default: MOZ_ASSERT_UNREACHABLE("Unexpected AssertedScopeKind"); break; } return Ok(); } template JS::Result BinASTParserPerTokenizer::checkBinding(JSAtom* name) { // Check that the variable appears in the corresponding scope. ParseContext::Scope& scope = variableDeclarationKind_ == VariableDeclarationKind::Var ? pc_->varScope() : *pc_->innermostScope(); auto ptr = scope.lookupDeclaredName(name->asPropertyName()); if (!ptr) { return raiseMissingVariableInAssertedScope(name); } return Ok(); } // Binary AST (revision 8eab67e0c434929a66ff6abe99ff790bca087dda) // 3.1.5 CheckPositionalParameterIndices. template JS::Result BinASTParserPerTokenizer::checkPositionalParameterIndices( Handle> positionalParams, ListNode* params) { // positionalParams should have the corresponding entry up to the last // positional parameter. // `positionalParams` corresponds to `expectedParams` parameter in the spec. // `params` corresponds to `parseTree` in 3.1.9 CheckAssertedScope, and // `positionalParamNames` parameter // Steps 1-3. // PositionalParameterNames (3.1.9 CheckAssertedScope step 5.d) and // CreatePositionalParameterIndices (3.1.5 CheckPositionalParameterIndices // step 1) are done implicitly. uint32_t i = 0; const bool hasRest = pc_->functionBox()->hasRest(); for (ParseNode* param : params->contents()) { if (param->isKind(ParseNodeKind::AssignExpr)) { param = param->as().left(); } // At this point, function body is not part of params list. const bool isRest = hasRest && !param->pn_next; if (isRest) { // Rest parameter // Step 3. if (i >= positionalParams.get().length()) { continue; } if (positionalParams.get()[i]) { return raiseError( "Expected positional parameter per " "AssertedParameterScope.paramNames, got rest parameter"); } } else if (param->isKind(ParseNodeKind::Name)) { // Simple or default parameter. // Step 2.a. if (i >= positionalParams.get().length()) { return raiseError( "AssertedParameterScope.paramNames doesn't have corresponding " "entry to positional parameter"); } JSAtom* name = positionalParams.get()[i]; if (!name) { // Step 2.a.ii.1. return raiseError( "Expected destructuring/rest parameter per " "AssertedParameterScope.paramNames, got positional parameter"); } // Step 2.a.i. if (param->as().name() != name) { // Step 2.a.ii.1. return raiseError( "Name mismatch between AssertedPositionalParameterName in " "AssertedParameterScope.paramNames and actual parameter"); } // Step 2.a.i.1. // Implicitly done. } else { // Destructuring parameter. MOZ_ASSERT(param->isKind(ParseNodeKind::ObjectExpr) || param->isKind(ParseNodeKind::ArrayExpr)); // Step 3. if (i >= positionalParams.get().length()) { continue; } if (positionalParams.get()[i]) { return raiseError( "Expected positional parameter per " "AssertedParameterScope.paramNames, got destructuring parameter"); } } i++; } // Step 3. if (positionalParams.get().length() > params->count()) { // `positionalParams` has unchecked entries. return raiseError( "AssertedParameterScope.paramNames has unmatching items than the " "actual parameters"); } return Ok(); } // Binary AST (revision 8eab67e0c434929a66ff6abe99ff790bca087dda) // 3.1.13 CheckFunctionLength. template JS::Result BinASTParserPerTokenizer::checkFunctionLength( uint32_t expectedLength) { if (pc_->functionBox()->length != expectedLength) { return raiseError("Function length does't match"); } return Ok(); } template JS::Result BinASTParserPerTokenizer::checkClosedVars( ParseContext::Scope& scope) { for (ParseContext::Scope::BindingIter bi = scope.bindings(pc_); bi; bi++) { if (UsedNamePtr p = usedNames_.lookup(bi.name())) { bool closedOver; p->value().noteBoundInScope(pc_->scriptId(), scope.id(), &closedOver); if (closedOver && !bi.closedOver()) { return raiseInvalidClosedVar(bi.name()); } } } return Ok(); } template JS::Result BinASTParserPerTokenizer::checkFunctionClosedVars() { MOZ_ASSERT(pc_->isFunctionBox()); MOZ_TRY(checkClosedVars(*pc_->innermostScope())); MOZ_TRY(checkClosedVars(pc_->functionScope())); if (pc_->functionBox()->isNamedLambda()) { MOZ_TRY(checkClosedVars(pc_->namedLambdaScope())); } return Ok(); } template JS::Result BinASTParserPerTokenizer::prependDirectivesToBody( ListNode* body, ListNode* directives) { if (!directives) { return Ok(); } if (directives->empty()) { return Ok(); } MOZ_TRY(prependDirectivesImpl(body, directives->head())); return Ok(); } template JS::Result BinASTParserPerTokenizer::prependDirectivesImpl( ListNode* body, ParseNode* directive) { BINJS_TRY(CheckRecursionLimit(cx_)); // Prepend in the reverse order by using stack, so that the directives are // prepended in the original order. if (ParseNode* next = directive->pn_next) { MOZ_TRY(prependDirectivesImpl(body, next)); } BINJS_TRY_DECL(statement, handler_.newExprStatement(directive, directive->pn_pos.end)); body->prependAndUpdatePos(statement); return Ok(); } template mozilla::GenericErrorResult BinASTParserPerTokenizer::raiseInvalidClosedVar(JSAtom* name) { return raiseError("Captured variable was not declared as captured"); } template mozilla::GenericErrorResult BinASTParserPerTokenizer::raiseMissingVariableInAssertedScope( JSAtom* name) { // For the moment, we don't trust inputs sufficiently to put the name // in an error message. return raiseError("Missing variable in AssertedScope"); } template mozilla::GenericErrorResult BinASTParserPerTokenizer::raiseMissingDirectEvalInAssertedScope() { return raiseError("Direct call to `eval` was not declared in AssertedScope"); } template mozilla::GenericErrorResult BinASTParserPerTokenizer::raiseInvalidKind(const char* superKind, const BinASTKind kind) { Sprinter out(cx_); BINJS_TRY(out.init()); BINJS_TRY(out.printf("In %s, invalid kind %s", superKind, describeBinASTKind(kind))); return raiseError(out.string()); } template mozilla::GenericErrorResult BinASTParserPerTokenizer::raiseInvalidVariant(const char* kind, const BinASTVariant value) { Sprinter out(cx_); BINJS_TRY(out.init()); BINJS_TRY(out.printf("In %s, invalid variant '%s'", kind, describeBinASTVariant(value))); return raiseError(out.string()); } template mozilla::GenericErrorResult BinASTParserPerTokenizer::raiseMissingField(const char* kind, const BinASTField field) { Sprinter out(cx_); BINJS_TRY(out.init()); BINJS_TRY(out.printf("In %s, missing field '%s'", kind, describeBinASTField(field))); return raiseError(out.string()); } template mozilla::GenericErrorResult BinASTParserPerTokenizer::raiseEmpty(const char* description) { Sprinter out(cx_); BINJS_TRY(out.init()); BINJS_TRY(out.printf("Empty %s", description)); return raiseError(out.string()); } template mozilla::GenericErrorResult BinASTParserPerTokenizer::raiseOOM() { return tokenizer_->raiseOOM(); } template mozilla::GenericErrorResult BinASTParserPerTokenizer::raiseError(BinASTKind kind, const char* description) { Sprinter out(cx_); BINJS_TRY(out.init()); BINJS_TRY(out.printf("In %s, %s", describeBinASTKind(kind), description)); return tokenizer_->raiseError(out.string()); } template mozilla::GenericErrorResult BinASTParserPerTokenizer::raiseError(const char* description) { return tokenizer_->raiseError(description); } template void BinASTParserPerTokenizer::poison() { tokenizer_.reset(); } template bool BinASTParserPerTokenizer::computeErrorMetadata( ErrorMetadata* err, const ErrorOffset& errorOffset) { err->filename = getFilename(); err->lineNumber = 0; if (errorOffset.is()) { err->columnNumber = errorOffset.as(); } else if (errorOffset.is()) { err->columnNumber = offset(); } else { errorOffset.is(); err->columnNumber = 0; } err->isMuted = options().mutedErrors(); return true; } void TraceBinASTParser(JSTracer* trc, JS::AutoGCRooter* parser) { static_cast(parser)->trace(trc); } template void BinASTParserPerTokenizer::doTrace(JSTracer* trc) { if (tokenizer_) { tokenizer_->traceMetadata(trc); } } template inline typename BinASTParserPerTokenizer::FinalParser* BinASTParserPerTokenizer::asFinalParser() { // Same as GeneralParser::asFinalParser, verify the inheritance to // make sure the static downcast works. static_assert( mozilla::IsBaseOf, FinalParser>::value, "inheritance relationship required by the static_cast<> below"); return static_cast(this); } template inline const typename BinASTParserPerTokenizer::FinalParser* BinASTParserPerTokenizer::asFinalParser() const { static_assert( mozilla::IsBaseOf, FinalParser>::value, "inheritance relationship required by the static_cast<> below"); return static_cast(this); } // Force class instantiation. // This ensures that the symbols are built, without having to export all our // code (and its baggage of #include and macros) in the header. template class BinASTParserPerTokenizer; template class BinASTParserPerTokenizer; } // namespace frontend } // namespace js