#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/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"
namespace js {
namespace frontend {
using UsedNamePtr = UsedNameTracker::UsedNameMap::Ptr;
template <typename Tok>
BinASTParserPerTokenizer<Tok>::BinASTParserPerTokenizer(
JSContext* cx, LifoAlloc& alloc, UsedNameTracker& usedNames,
const JS::ReadOnlyCompileOptions& options,
HandleScriptSourceObject sourceObject,
Handle<LazyScript*> lazyScript )
: 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 <typename Tok>
JS::Result<ParseNode*> BinASTParserPerTokenizer<Tok>::parse(
GlobalSharedContext* globalsc, const Vector<uint8_t>& data,
BinASTSourceMetadata** metadataPtr) {
return parse(globalsc, data.begin(), data.length(), metadataPtr);
}
template <typename Tok>
JS::Result<ParseNode*> BinASTParserPerTokenizer<Tok>::parse(
GlobalSharedContext* globalsc, const uint8_t* start, const size_t length,
BinASTSourceMetadata** metadataPtr) {
auto result = parseAux(globalsc, start, length, metadataPtr);
poison(); return result;
}
template <typename Tok>
JS::Result<ParseNode*> BinASTParserPerTokenizer<Tok>::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,
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);
MOZ_TRY_VAR(result, asFinalParser()->parseProgram());
mozilla::Maybe<GlobalScope::Data*> bindings =
NewGlobalScopeData(cx_, varScope, alloc_, pc_);
if (!bindings) {
return cx_->alreadyReportedError();
}
globalsc->bindings = *bindings;
if (metadataPtr) {
*metadataPtr = tokenizer_->takeMetadata();
}
return result; }
template <typename Tok>
JS::Result<FunctionNode*> BinASTParserPerTokenizer<Tok>::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);
RootedFunction func(cx_, lazyScript_->functionNonDelazifying());
bool isExpr = func->isLambda();
MOZ_ASSERT(func->kind() == JSFunction::FunctionKind::NormalFunction);
auto onExit = mozilla::MakeScopeExit([&]() { poison(); });
auto syntaxKind =
isExpr ? FunctionSyntaxKind::Expression : FunctionSyntaxKind::Statement;
BINJS_MOZ_TRY_DECL(
funbox, buildFunctionBox(lazyScript_->generatorKind(),
lazyScript_->asyncKind(), syntaxKind, nullptr));
BinASTParseContext funpc(cx_, this, funbox, 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;
MOZ_TRY((asFinalParser()->*parseFunc)(func->nargs(), ¶ms, &tmpBody));
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 <typename Tok>
void BinASTParserPerTokenizer<Tok>::forceStrictIfNecessary(
SharedContext* sc, ListNode* directives) {
JSAtom* useStrict = cx_->names().useStrict;
for (const ParseNode* directive : directives->contents()) {
if (directive->as<NameNode>().atom() == useStrict) {
sc->strictScript = true;
break;
}
}
}
template <typename Tok>
JS::Result<FunctionBox*> BinASTParserPerTokenizer<Tok>::buildFunctionBox(
GeneratorKind generatorKind, FunctionAsyncKind functionAsyncKind,
FunctionSyntaxKind syntax, ParseNode* name) {
MOZ_ASSERT_IF(!pc_, lazyScript_);
RootedAtom atom(cx_);
if (name && name->is<NameNode>()) {
atom = name->as<NameNode>().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);
}
}
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> directives;
if (pc_) {
directives.emplace(pc_);
} else {
directives.emplace(lazyScript_->strict());
}
auto* funbox = alloc_.new_<FunctionBox>(
cx_, traceListHead_, fun, 0, *directives,
false, generatorKind, functionAsyncKind);
if (!funbox) {
return raiseOOM();
}
traceListHead_ = funbox;
if (pc_) {
funbox->initWithEnclosingParseContext(pc_, syntax);
} else {
funbox->initFromLazyFunction();
}
return funbox;
}
FunctionSyntaxKind BinASTKindToFunctionSyntaxKind(const BinASTKind kind) {
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 <typename Tok>
JS::Result<FunctionNode*> BinASTParserPerTokenizer<Tok>::makeEmptyFunctionNode(
const size_t start, const BinASTKind kind, FunctionBox* funbox) {
TokenPos pos = tokenizer_->pos(start);
FunctionSyntaxKind syntaxKind = BinASTKindToFunctionSyntaxKind(kind);
BINJS_TRY_DECL(result, handler_.newFunction(syntaxKind, pos));
handler_.setFunctionBox(result, funbox);
return result;
}
template <typename Tok>
JS::Result<FunctionNode*> BinASTParserPerTokenizer<Tok>::buildFunction(
const size_t start, const BinASTKind kind, ParseNode* name,
ListNode* params, ParseNode* body) {
FunctionBox* funbox = pc_->functionBox();
if (!lazyScript_ ||
lazyScript_->functionNonDelazifying() != funbox->function()) {
funbox->function()->setArgCount(params ? uint16_t(params->count()) : 0);
}
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<LexicalScopeNode>().scopeBody()->as<ListNode>();
BINJS_TRY(handler_.prependInitialYield(stmtList, dotGen));
}
}
const bool canSkipLazyClosedOverBindings = false;
BINJS_TRY(pc_->declareFunctionArgumentsObject(usedNames_,
canSkipLazyClosedOverBindings));
BINJS_TRY(
pc_->declareFunctionThis(usedNames_, canSkipLazyClosedOverBindings));
MOZ_TRY(checkFunctionClosedVars());
BINJS_TRY_DECL(bindings, NewFunctionScopeData(cx_, pc_->functionScope(),
false,
alloc_, pc_));
funbox->functionScopeBindings().set(*bindings);
if (funbox->function()->isNamedLambda()) {
BINJS_TRY_DECL(
recursiveBinding,
NewLexicalScopeData(cx_, pc_->namedLambdaScope(), alloc_, pc_));
funbox->namedLambdaBindings().set(*recursiveBinding);
}
return result;
}
template <typename Tok>
JS::Result<Ok> BinASTParserPerTokenizer<Tok>::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 <typename Tok>
void BinASTParserPerTokenizer<Tok>::captureFunctionName() {
MOZ_ASSERT(pc_->isFunctionBox());
MOZ_ASSERT(pc_->functionBox()->function()->isNamedLambda());
RootedAtom funName(cx_, pc_->functionBox()->function()->explicitName());
MOZ_ASSERT(funName);
auto ptr = pc_->namedLambdaScope().lookupDeclaredName(funName);
MOZ_ASSERT(ptr);
ptr->value()->setClosedOver();
}
template <typename Tok>
JS::Result<Ok> BinASTParserPerTokenizer<Tok>::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 <typename Tok>
JS::Result<Ok> BinASTParserPerTokenizer<Tok>::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 <typename Tok>
JS::Result<Ok> BinASTParserPerTokenizer<Tok>::checkBinding(JSAtom* name) {
ParseContext::Scope& scope =
variableDeclarationKind_ == VariableDeclarationKind::Var
? pc_->varScope()
: *pc_->innermostScope();
auto ptr = scope.lookupDeclaredName(name->asPropertyName());
if (!ptr) {
return raiseMissingVariableInAssertedScope(name);
}
return Ok();
}
template <typename Tok>
JS::Result<Ok> BinASTParserPerTokenizer<Tok>::checkPositionalParameterIndices(
Handle<GCVector<JSAtom*>> positionalParams, ListNode* params) {
uint32_t i = 0;
const bool hasRest = pc_->functionBox()->hasRest();
for (ParseNode* param : params->contents()) {
if (param->isKind(ParseNodeKind::AssignExpr)) {
param = param->as<AssignmentNode>().left();
}
const bool isRest = hasRest && !param->pn_next;
if (isRest) {
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)) {
if (i >= positionalParams.get().length()) {
return raiseError(
"AssertedParameterScope.paramNames doesn't have corresponding "
"entry to positional parameter");
}
JSAtom* name = positionalParams.get()[i];
if (!name) {
return raiseError(
"Expected destructuring/rest parameter per "
"AssertedParameterScope.paramNames, got positional parameter");
}
if (param->as<NameNode>().name() != name) {
return raiseError(
"Name mismatch between AssertedPositionalParameterName in "
"AssertedParameterScope.paramNames and actual parameter");
}
} else {
MOZ_ASSERT(param->isKind(ParseNodeKind::ObjectExpr) ||
param->isKind(ParseNodeKind::ArrayExpr));
if (i >= positionalParams.get().length()) {
continue;
}
if (positionalParams.get()[i]) {
return raiseError(
"Expected positional parameter per "
"AssertedParameterScope.paramNames, got destructuring parameter");
}
}
i++;
}
if (positionalParams.get().length() > params->count()) {
return raiseError(
"AssertedParameterScope.paramNames has unmatching items than the "
"actual parameters");
}
return Ok();
}
template <typename Tok>
JS::Result<Ok> BinASTParserPerTokenizer<Tok>::checkFunctionLength(
uint32_t expectedLength) {
if (pc_->functionBox()->length != expectedLength) {
return raiseError("Function length does't match");
}
return Ok();
}
template <typename Tok>
JS::Result<Ok> BinASTParserPerTokenizer<Tok>::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 <typename Tok>
JS::Result<Ok> BinASTParserPerTokenizer<Tok>::checkFunctionClosedVars() {
MOZ_ASSERT(pc_->isFunctionBox());
MOZ_TRY(checkClosedVars(*pc_->innermostScope()));
MOZ_TRY(checkClosedVars(pc_->functionScope()));
if (pc_->functionBox()->function()->isNamedLambda()) {
MOZ_TRY(checkClosedVars(pc_->namedLambdaScope()));
}
return Ok();
}
template <typename Tok>
JS::Result<Ok> BinASTParserPerTokenizer<Tok>::prependDirectivesToBody(
ListNode* body, ListNode* directives) {
if (!directives) {
return Ok();
}
if (directives->empty()) {
return Ok();
}
MOZ_TRY(prependDirectivesImpl(body, directives->head()));
return Ok();
}
template <typename Tok>
JS::Result<Ok> BinASTParserPerTokenizer<Tok>::prependDirectivesImpl(
ListNode* body, ParseNode* directive) {
BINJS_TRY(CheckRecursionLimit(cx_));
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 <typename Tok>
mozilla::GenericErrorResult<JS::Error&>
BinASTParserPerTokenizer<Tok>::raiseInvalidClosedVar(JSAtom* name) {
return raiseError("Captured variable was not declared as captured");
}
template <typename Tok>
mozilla::GenericErrorResult<JS::Error&>
BinASTParserPerTokenizer<Tok>::raiseMissingVariableInAssertedScope(
JSAtom* name) {
return raiseError("Missing variable in AssertedScope");
}
template <typename Tok>
mozilla::GenericErrorResult<JS::Error&>
BinASTParserPerTokenizer<Tok>::raiseMissingDirectEvalInAssertedScope() {
return raiseError("Direct call to `eval` was not declared in AssertedScope");
}
template <typename Tok>
mozilla::GenericErrorResult<JS::Error&>
BinASTParserPerTokenizer<Tok>::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 <typename Tok>
mozilla::GenericErrorResult<JS::Error&>
BinASTParserPerTokenizer<Tok>::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 <typename Tok>
mozilla::GenericErrorResult<JS::Error&>
BinASTParserPerTokenizer<Tok>::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 <typename Tok>
mozilla::GenericErrorResult<JS::Error&>
BinASTParserPerTokenizer<Tok>::raiseEmpty(const char* description) {
Sprinter out(cx_);
BINJS_TRY(out.init());
BINJS_TRY(out.printf("Empty %s", description));
return raiseError(out.string());
}
template <typename Tok>
mozilla::GenericErrorResult<JS::Error&>
BinASTParserPerTokenizer<Tok>::raiseOOM() {
return tokenizer_->raiseOOM();
}
template <typename Tok>
mozilla::GenericErrorResult<JS::Error&>
BinASTParserPerTokenizer<Tok>::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 <typename Tok>
mozilla::GenericErrorResult<JS::Error&>
BinASTParserPerTokenizer<Tok>::raiseError(const char* description) {
return tokenizer_->raiseError(description);
}
template <typename Tok>
void BinASTParserPerTokenizer<Tok>::poison() {
tokenizer_.reset();
}
template <typename Tok>
bool BinASTParserPerTokenizer<Tok>::computeErrorMetadata(
ErrorMetadata* err, const ErrorOffset& errorOffset) {
err->filename = getFilename();
err->lineNumber = 0;
if (errorOffset.is<uint32_t>()) {
err->columnNumber = errorOffset.as<uint32_t>();
} else if (errorOffset.is<Current>()) {
err->columnNumber = offset();
} else {
errorOffset.is<NoOffset>();
err->columnNumber = 0;
}
err->isMuted = options().mutedErrors();
return true;
}
void TraceBinASTParser(JSTracer* trc, JS::AutoGCRooter* parser) {
static_cast<BinASTParserBase*>(parser)->trace(trc);
}
template <typename Tok>
void BinASTParserPerTokenizer<Tok>::doTrace(JSTracer* trc) {
if (tokenizer_) {
tokenizer_->traceMetadata(trc);
}
}
template <typename Tok>
inline typename BinASTParserPerTokenizer<Tok>::FinalParser*
BinASTParserPerTokenizer<Tok>::asFinalParser() {
static_assert(
mozilla::IsBaseOf<BinASTParserPerTokenizer<Tok>, FinalParser>::value,
"inheritance relationship required by the static_cast<> below");
return static_cast<FinalParser*>(this);
}
template <typename Tok>
inline const typename BinASTParserPerTokenizer<Tok>::FinalParser*
BinASTParserPerTokenizer<Tok>::asFinalParser() const {
static_assert(
mozilla::IsBaseOf<BinASTParserPerTokenizer<Tok>, FinalParser>::value,
"inheritance relationship required by the static_cast<> below");
return static_cast<const FinalParser*>(this);
}
template class BinASTParserPerTokenizer<BinASTTokenReaderMultipart>;
} }