Source code

Revision control

Other Tools

/* -*- 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/. */
/*
* JS parser.
*
* This is a recursive-descent parser for the JavaScript language specified by
* "The ECMAScript Language Specification" (Standard ECMA-262). It uses
* lexical and semantic feedback to disambiguate non-LL(1) structures. It
* generates trees of nodes induced by the recursive parsing (not precise
* syntax trees, see Parser.h). After tree construction, it rewrites trees to
* fold constants and evaluate compile-time expressions.
*
* This parser attempts no error recovery.
*/
#include "frontend/Parser.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/Casting.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/Range.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/Sprintf.h"
#include "mozilla/Unused.h"
#include "mozilla/Utf8.h"
#include "mozilla/Variant.h"
#include <memory>
#include <new>
#include <type_traits>
#include "jsnum.h"
#include "jstypes.h"
#include "builtin/ModuleObject.h"
#include "builtin/SelfHostingDefines.h"
#include "frontend/BytecodeCompiler.h"
#include "frontend/BytecodeSection.h"
#include "frontend/FoldConstants.h"
#include "frontend/FunctionSyntaxKind.h" // FunctionSyntaxKind
#include "frontend/ModuleSharedContext.h"
#include "frontend/ParseNode.h"
#include "frontend/ParseNodeVerify.h"
#include "frontend/TokenStream.h"
#include "irregexp/RegExpAPI.h"
#include "js/RegExpFlags.h" // JS::RegExpFlags
#include "util/StringBuffer.h" // StringBuffer
#include "vm/BigIntType.h"
#include "vm/BytecodeUtil.h"
#include "vm/FunctionFlags.h" // js::FunctionFlags
#include "vm/GeneratorAndAsyncKind.h" // js::GeneratorKind, js::FunctionAsyncKind
#include "vm/JSAtom.h"
#include "vm/JSContext.h"
#include "vm/JSFunction.h"
#include "vm/JSScript.h"
#include "vm/ModuleBuilder.h" // js::ModuleBuilder
#include "vm/RegExpObject.h"
#include "vm/SelfHosting.h"
#include "vm/StringType.h"
#include "wasm/AsmJS.h"
#include "frontend/ParseContext-inl.h"
#include "frontend/SharedContext-inl.h"
#include "vm/EnvironmentObject-inl.h"
using namespace js;
using mozilla::AssertedCast;
using mozilla::AsVariant;
using mozilla::Maybe;
using mozilla::Nothing;
using mozilla::PointerRangeSize;
using mozilla::Some;
using mozilla::Utf8Unit;
using JS::AutoGCRooter;
using JS::ReadOnlyCompileOptions;
using JS::RegExpFlags;
namespace js::frontend {
using DeclaredNamePtr = ParseContext::Scope::DeclaredNamePtr;
using AddDeclaredNamePtr = ParseContext::Scope::AddDeclaredNamePtr;
using BindingIter = ParseContext::Scope::BindingIter;
using UsedNamePtr = UsedNameTracker::UsedNameMap::Ptr;
using ParserBindingNameVector = Vector<ParserBindingName, 6>;
template <class T, class U>
static inline void PropagateTransitiveParseFlags(const T* inner, U* outer) {
if (inner->bindingsAccessedDynamically()) {
outer->setBindingsAccessedDynamically();
}
if (inner->hasDirectEval()) {
outer->setHasDirectEval();
}
}
static bool StatementKindIsBraced(StatementKind kind) {
return kind == StatementKind::Block || kind == StatementKind::Switch ||
kind == StatementKind::Try || kind == StatementKind::Catch ||
kind == StatementKind::Finally;
}
template <class ParseHandler, typename Unit>
inline typename GeneralParser<ParseHandler, Unit>::FinalParser*
GeneralParser<ParseHandler, Unit>::asFinalParser() {
static_assert(
std::is_base_of_v<GeneralParser<ParseHandler, Unit>, FinalParser>,
"inheritance relationship required by the static_cast<> below");
return static_cast<FinalParser*>(this);
}
template <class ParseHandler, typename Unit>
inline const typename GeneralParser<ParseHandler, Unit>::FinalParser*
GeneralParser<ParseHandler, Unit>::asFinalParser() const {
static_assert(
std::is_base_of_v<GeneralParser<ParseHandler, Unit>, FinalParser>,
"inheritance relationship required by the static_cast<> below");
return static_cast<const FinalParser*>(this);
}
template <class ParseHandler, typename Unit>
template <typename ConditionT, typename ErrorReportT>
bool GeneralParser<ParseHandler, Unit>::mustMatchTokenInternal(
ConditionT condition, ErrorReportT errorReport) {
MOZ_ASSERT(condition(TokenKind::Div) == false);
MOZ_ASSERT(condition(TokenKind::DivAssign) == false);
MOZ_ASSERT(condition(TokenKind::RegExp) == false);
TokenKind actual;
if (!tokenStream.getToken(&actual, TokenStream::SlashIsInvalid)) {
return false;
}
if (!condition(actual)) {
errorReport(actual);
return false;
}
return true;
}
ParserSharedBase::ParserSharedBase(JSContext* cx,
CompilationInfo& compilationInfo,
CompilationState& compilationState,
Kind kind)
: cx_(cx),
alloc_(compilationState.allocScope.alloc()),
compilationInfo_(compilationInfo),
compilationState_(compilationState),
pc_(nullptr),
usedNames_(compilationState.usedNames) {
cx->frontendCollectionPool().addActiveCompilation();
}
ParserSharedBase::~ParserSharedBase() {
cx_->frontendCollectionPool().removeActiveCompilation();
}
ParserBase::ParserBase(JSContext* cx, const ReadOnlyCompileOptions& options,
bool foldConstants, CompilationInfo& compilationInfo,
CompilationState& compilationState)
: ParserSharedBase(cx, compilationInfo, compilationState,
ParserSharedBase::Kind::Parser),
anyChars(cx, options, this),
ss(nullptr),
foldConstants_(foldConstants),
#ifdef DEBUG
checkOptionsCalled_(false),
#endif
isUnexpectedEOF_(false),
awaitHandling_(AwaitIsName),
inParametersOfAsyncFunction_(false) {
}
bool ParserBase::checkOptions() {
#ifdef DEBUG
checkOptionsCalled_ = true;
#endif
return anyChars.checkOptions();
}
ParserBase::~ParserBase() { MOZ_ASSERT(checkOptionsCalled_); }
template <class ParseHandler>
PerHandlerParser<ParseHandler>::PerHandlerParser(
JSContext* cx, const ReadOnlyCompileOptions& options, bool foldConstants,
CompilationInfo& compilationInfo, CompilationState& compilationState,
BaseScript* lazyOuterFunction, void* internalSyntaxParser)
: ParserBase(cx, options, foldConstants, compilationInfo, compilationState),
handler_(cx, compilationState.allocScope.alloc(), lazyOuterFunction),
internalSyntaxParser_(internalSyntaxParser) {}
template <class ParseHandler, typename Unit>
GeneralParser<ParseHandler, Unit>::GeneralParser(
JSContext* cx, const ReadOnlyCompileOptions& options, const Unit* units,
size_t length, bool foldConstants, CompilationInfo& compilationInfo,
CompilationState& compilationState, SyntaxParser* syntaxParser,
BaseScript* lazyOuterFunction)
: Base(cx, options, foldConstants, compilationInfo, compilationState,
syntaxParser, lazyOuterFunction),
tokenStream(cx, &compilationInfo.stencil.parserAtoms, options, units,
length) {}
template <typename Unit>
void Parser<SyntaxParseHandler, Unit>::setAwaitHandling(
AwaitHandling awaitHandling) {
this->awaitHandling_ = awaitHandling;
}
template <typename Unit>
void Parser<FullParseHandler, Unit>::setAwaitHandling(
AwaitHandling awaitHandling) {
this->awaitHandling_ = awaitHandling;
if (SyntaxParser* syntaxParser = getSyntaxParser()) {
syntaxParser->setAwaitHandling(awaitHandling);
}
}
template <class ParseHandler, typename Unit>
inline void GeneralParser<ParseHandler, Unit>::setAwaitHandling(
AwaitHandling awaitHandling) {
asFinalParser()->setAwaitHandling(awaitHandling);
}
template <typename Unit>
void Parser<SyntaxParseHandler, Unit>::setInParametersOfAsyncFunction(
bool inParameters) {
this->inParametersOfAsyncFunction_ = inParameters;
}
template <typename Unit>
void Parser<FullParseHandler, Unit>::setInParametersOfAsyncFunction(
bool inParameters) {
this->inParametersOfAsyncFunction_ = inParameters;
if (SyntaxParser* syntaxParser = getSyntaxParser()) {
syntaxParser->setInParametersOfAsyncFunction(inParameters);
}
}
template <class ParseHandler, typename Unit>
inline void GeneralParser<ParseHandler, Unit>::setInParametersOfAsyncFunction(
bool inParameters) {
asFinalParser()->setInParametersOfAsyncFunction(inParameters);
}
template <class ParseHandler>
FunctionBox* PerHandlerParser<ParseHandler>::newFunctionBox(
FunctionNodeType funNode, const ParserAtom* explicitName,
FunctionFlags flags, uint32_t toStringStart, Directives inheritedDirectives,
GeneratorKind generatorKind, FunctionAsyncKind asyncKind,
TopLevelFunction isTopLevel) {
MOZ_ASSERT(funNode);
FunctionIndex index =
FunctionIndex(compilationInfo_.stencil.scriptData.length());
MOZ_ASSERT_IF(isTopLevel == TopLevelFunction::Yes,
index == CompilationInfo::TopLevelIndex);
if (!compilationInfo_.stencil.scriptData.emplaceBack()) {
js::ReportOutOfMemory(cx_);
return nullptr;
}
// This source extent will be further filled in during the remainder of parse.
SourceExtent extent;
extent.toStringStart = toStringStart;
/*
* 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.
*/
FunctionBox* funbox = alloc_.new_<FunctionBox>(
cx_, extent, compilationInfo_, compilationState_, inheritedDirectives,
generatorKind, asyncKind, explicitName, flags, index, isTopLevel);
if (!funbox) {
ReportOutOfMemory(cx_);
return nullptr;
}
handler_.setFunctionBox(funNode, funbox);
return funbox;
}
bool ParserBase::setSourceMapInfo() {
// If support for source pragmas have been fully disabled, we can skip
// processing of all of these values.
if (!options().sourcePragmas()) {
return true;
}
// Not all clients initialize ss. Can't update info to an object that isn't
// there.
if (!ss) {
return true;
}
if (anyChars.hasDisplayURL()) {
if (!ss->setDisplayURL(cx_, anyChars.displayURL())) {
return false;
}
}
if (anyChars.hasSourceMapURL()) {
MOZ_ASSERT(!ss->hasSourceMapURL());
if (!ss->setSourceMapURL(cx_, anyChars.sourceMapURL())) {
return false;
}
}
/*
* Source map URLs passed as a compile option (usually via a HTTP source map
* header) override any source map urls passed as comment pragmas.
*/
if (options().sourceMapURL()) {
// Warn about the replacement, but use the new one.
if (ss->hasSourceMapURL()) {
if (!warningNoOffset(JSMSG_ALREADY_HAS_PRAGMA, ss->filename(),
"//# sourceMappingURL")) {
return false;
}
}
if (!ss->setSourceMapURL(cx_, options().sourceMapURL())) {
return false;
}
}
return true;
}
/*
* Parse a top-level JS script.
*/
template <class ParseHandler, typename Unit>
typename ParseHandler::ListNodeType GeneralParser<ParseHandler, Unit>::parse() {
MOZ_ASSERT(checkOptionsCalled_);
SourceExtent extent = SourceExtent::makeGlobalExtent(
/* len = */ 0, options().lineno, options().column);
Directives directives(options().forceStrictMode());
GlobalSharedContext globalsc(cx_, ScopeKind::Global,
this->getCompilationInfo(), directives, extent);
SourceParseContext globalpc(this, &globalsc, /* newDirectives = */ nullptr);
if (!globalpc.init()) {
return null();
}
ParseContext::VarScope varScope(this);
if (!varScope.init(pc_)) {
return null();
}
ListNodeType stmtList = statementList(YieldIsName);
if (!stmtList) {
return null();
}
TokenKind tt;
if (!tokenStream.getToken(&tt, TokenStream::SlashIsRegExp)) {
return null();
}
if (tt != TokenKind::Eof) {
error(JSMSG_GARBAGE_AFTER_INPUT, "script", TokenKindToDesc(tt));
return null();
}
if (!CheckParseTree(cx_, alloc_, stmtList)) {
return null();
}
if (foldConstants_) {
Node node = stmtList;
// Don't constant-fold inside "use asm" code, as this could create a parse
// tree that doesn't type-check as asm.js.
if (!pc_->useAsmOrInsideUseAsm()) {
if (!FoldConstants(cx_, this->getCompilationInfo().stencil.parserAtoms,
&node, &handler_)) {
return null();
}
}
stmtList = handler_.asList(node);
}
return stmtList;
}
/*
* Strict mode forbids introducing new definitions for 'eval', 'arguments',
* 'let', 'static', 'yield', or for any strict mode reserved word.
*/
bool ParserBase::isValidStrictBinding(const ParserName* name) {
TokenKind tt = ReservedWordTokenKind(name);
if (tt == TokenKind::Name) {
return name != cx_->parserNames().eval &&
name != cx_->parserNames().arguments;
}
return tt != TokenKind::Let && tt != TokenKind::Static &&
tt != TokenKind::Yield && !TokenKindIsStrictReservedWord(tt);
}
/*
* Returns true if all parameter names are valid strict mode binding names and
* no duplicate parameter names are present.
*/
bool ParserBase::hasValidSimpleStrictParameterNames() {
MOZ_ASSERT(pc_->isFunctionBox() &&
pc_->functionBox()->hasSimpleParameterList());
if (pc_->functionBox()->hasDuplicateParameters) {
return false;
}
for (auto name : pc_->positionalFormalParameterNames()) {
MOZ_ASSERT(name);
if (!isValidStrictBinding(name->asName())) {
return false;
}
}
return true;
}
template <class ParseHandler, typename Unit>
void GeneralParser<ParseHandler, Unit>::reportMissingClosing(
unsigned errorNumber, unsigned noteNumber, uint32_t openedPos) {
auto notes = MakeUnique<JSErrorNotes>();
if (!notes) {
ReportOutOfMemory(pc_->sc()->cx_);
return;
}
uint32_t line, column;
tokenStream.computeLineAndColumn(openedPos, &line, &column);
const size_t MaxWidth = sizeof("4294967295");
char columnNumber[MaxWidth];
SprintfLiteral(columnNumber, "%" PRIu32, column);
char lineNumber[MaxWidth];
SprintfLiteral(lineNumber, "%" PRIu32, line);
if (!notes->addNoteASCII(pc_->sc()->cx_, getFilename(), 0, line, column,
GetErrorMessage, nullptr, noteNumber, lineNumber,
columnNumber)) {
return;
}
errorWithNotes(std::move(notes), errorNumber);
}
template <class ParseHandler, typename Unit>
void GeneralParser<ParseHandler, Unit>::reportRedeclaration(
const ParserName* name, DeclarationKind prevKind, TokenPos pos,
uint32_t prevPos) {
UniqueChars bytes = ParserAtomToPrintableString(cx_, name);
if (!bytes) {
return;
}
if (prevPos == DeclaredNameInfo::npos) {
errorAt(pos.begin, JSMSG_REDECLARED_VAR, DeclarationKindString(prevKind),
bytes.get());
return;
}
auto notes = MakeUnique<JSErrorNotes>();
if (!notes) {
ReportOutOfMemory(pc_->sc()->cx_);
return;
}
uint32_t line, column;
tokenStream.computeLineAndColumn(prevPos, &line, &column);
const size_t MaxWidth = sizeof("4294967295");
char columnNumber[MaxWidth];
SprintfLiteral(columnNumber, "%" PRIu32, column);
char lineNumber[MaxWidth];
SprintfLiteral(lineNumber, "%" PRIu32, line);
if (!notes->addNoteASCII(pc_->sc()->cx_, getFilename(), 0, line, column,
GetErrorMessage, nullptr, JSMSG_REDECLARED_PREV,
lineNumber, columnNumber)) {
return;
}
errorWithNotesAt(std::move(notes), pos.begin, JSMSG_REDECLARED_VAR,
DeclarationKindString(prevKind), bytes.get());
}
// notePositionalFormalParameter is called for both the arguments of a regular
// function definition and the arguments specified by the Function
// constructor.
//
// The 'disallowDuplicateParams' bool indicates whether the use of another
// feature (destructuring or default arguments) disables duplicate arguments.
// (ECMA-262 requires us to support duplicate parameter names, but, for newer
// features, we consider the code to have "opted in" to higher standards and
// forbid duplicates.)
template <class ParseHandler, typename Unit>
bool GeneralParser<ParseHandler, Unit>::notePositionalFormalParameter(
FunctionNodeType funNode, const ParserName* name, uint32_t beginPos,
bool disallowDuplicateParams, bool* duplicatedParam) {
if (AddDeclaredNamePtr p =
pc_->functionScope().lookupDeclaredNameForAdd(name)) {
if (disallowDuplicateParams) {
error(JSMSG_BAD_DUP_ARGS);
return false;
}
// Strict-mode disallows duplicate args. We may not know whether we are
// in strict mode or not (since the function body hasn't been parsed).
// In such cases, report will queue up the potential error and return
// 'true'.
if (pc_->sc()->strict()) {
UniqueChars bytes = ParserAtomToPrintableString(cx_, name);
if (!bytes) {
return false;
}
if (!strictModeError(JSMSG_DUPLICATE_FORMAL, bytes.get())) {
return false;
}
}
*duplicatedParam = true;
} else {
DeclarationKind kind = DeclarationKind::PositionalFormalParameter;
if (!pc_->functionScope().addDeclaredName(pc_, p, name, kind, beginPos)) {
return false;
}
}
if (!pc_->positionalFormalParameterNames().append(name)) {
ReportOutOfMemory(cx_);
return false;
}
NameNodeType paramNode = newName(name);
if (!paramNode) {
return false;
}
handler_.addFunctionFormalParameter(funNode, paramNode);
return true;
}
template <class ParseHandler>
bool PerHandlerParser<ParseHandler>::noteDestructuredPositionalFormalParameter(
FunctionNodeType funNode, Node destruct) {
// Append an empty name to the positional formals vector to keep track of
// argument slots when making ParserFunctionScopeData.
if (!pc_->positionalFormalParameterNames().append(nullptr)) {
ReportOutOfMemory(cx_);
return false;
}
handler_.addFunctionFormalParameter(funNode, destruct);
return true;
}
template <class ParseHandler, typename Unit>
bool GeneralParser<ParseHandler, Unit>::noteDeclaredName(const ParserName* name,
DeclarationKind kind,
TokenPos pos) {
// The asm.js validator does all its own symbol-table management so, as an
// optimization, avoid doing any work here.
if (pc_->useAsmOrInsideUseAsm()) {
return true;
}
switch (kind) {
case DeclarationKind::Var:
case DeclarationKind::BodyLevelFunction: {
Maybe<DeclarationKind> redeclaredKind;
uint32_t prevPos;
if (!pc_->tryDeclareVar(name, kind, pos.begin, &redeclaredKind,
&prevPos)) {
return false;
}
if (redeclaredKind) {
reportRedeclaration(name, *redeclaredKind, pos, prevPos);
return false;
}
break;
}
case DeclarationKind::ModuleBodyLevelFunction: {
MOZ_ASSERT(pc_->atModuleLevel());
AddDeclaredNamePtr p = pc_->varScope().lookupDeclaredNameForAdd(name);
if (p) {
reportRedeclaration(name, p->value()->kind(), pos, p->value()->pos());
return false;
}
if (!pc_->varScope().addDeclaredName(pc_, p, name, kind, pos.begin)) {
return false;
}
// Body-level functions in modules are always closed over.
pc_->varScope().lookupDeclaredName(name)->value()->setClosedOver();
break;
}
case DeclarationKind::FormalParameter: {
// It is an early error if any non-positional formal parameter name
// (e.g., destructuring formal parameter) is duplicated.
AddDeclaredNamePtr p =
pc_->functionScope().lookupDeclaredNameForAdd(name);
if (p) {
error(JSMSG_BAD_DUP_ARGS);
return false;
}
if (!pc_->functionScope().addDeclaredName(pc_, p, name, kind,
pos.begin)) {
return false;
}
break;
}
case DeclarationKind::LexicalFunction:
case DeclarationKind::PrivateName: {
ParseContext::Scope* scope = pc_->innermostScope();
AddDeclaredNamePtr p = scope->lookupDeclaredNameForAdd(name);
if (p) {
reportRedeclaration(name, p->value()->kind(), pos, p->value()->pos());
return false;
}
if (!scope->addDeclaredName(pc_, p, name, kind, pos.begin)) {
return false;
}
break;
}
case DeclarationKind::SloppyLexicalFunction: {
// Functions in block have complex allowances in sloppy mode for being
// labelled that other lexical declarations do not have. Those checks
// are more complex than calling checkLexicalDeclarationDirectlyWithin-
// Block and are done in checkFunctionDefinition.
ParseContext::Scope* scope = pc_->innermostScope();
if (AddDeclaredNamePtr p = scope->lookupDeclaredNameForAdd(name)) {
// It is usually an early error if there is another declaration
// with the same name in the same scope.
//
// Sloppy lexical functions may redeclare other sloppy lexical
// functions for web compatibility reasons.
if (p->value()->kind() != DeclarationKind::SloppyLexicalFunction) {
reportRedeclaration(name, p->value()->kind(), pos, p->value()->pos());
return false;
}
} else {
if (!scope->addDeclaredName(pc_, p, name, kind, pos.begin)) {
return false;
}
}
break;
}
case DeclarationKind::Let:
case DeclarationKind::Const:
case DeclarationKind::Class:
// The BoundNames of LexicalDeclaration and ForDeclaration must not
// contain 'let'. (CatchParameter is the only lexical binding form
// without this restriction.)
if (name == cx_->parserNames().let) {
errorAt(pos.begin, JSMSG_LEXICAL_DECL_DEFINES_LET);
return false;
}
// For body-level lexically declared names in a function, it is an
// early error if there is a formal parameter of the same name. This
// needs a special check if there is an extra var scope due to
// parameter expressions.
if (pc_->isFunctionExtraBodyVarScopeInnermost()) {
DeclaredNamePtr p = pc_->functionScope().lookupDeclaredName(name);
if (p && DeclarationKindIsParameter(p->value()->kind())) {
reportRedeclaration(name, p->value()->kind(), pos, p->value()->pos());
return false;
}
}
[[fallthrough]];
case DeclarationKind::Import:
// Module code is always strict, so 'let' is always a keyword and never a
// name.
MOZ_ASSERT(name != cx_->parserNames().let);
[[fallthrough]];
case DeclarationKind::SimpleCatchParameter:
case DeclarationKind::CatchParameter: {
ParseContext::Scope* scope = pc_->innermostScope();
// It is an early error if there is another declaration with the same
// name in the same scope.
AddDeclaredNamePtr p = scope->lookupDeclaredNameForAdd(name);
if (p) {
reportRedeclaration(name, p->value()->kind(), pos, p->value()->pos());
return false;
}
if (!scope->addDeclaredName(pc_, p, name, kind, pos.begin)) {
return false;
}
break;
}
case DeclarationKind::CoverArrowParameter:
// CoverArrowParameter is only used as a placeholder declaration kind.
break;
case DeclarationKind::PositionalFormalParameter:
MOZ_CRASH(
"Positional formal parameter names should use "
"notePositionalFormalParameter");
break;
case DeclarationKind::VarForAnnexBLexicalFunction:
MOZ_CRASH(
"Synthesized Annex B vars should go through "
"tryDeclareVarForAnnexBLexicalFunction");
break;
}
return true;
}
template <class ParseHandler, typename Unit>
bool GeneralParser<ParseHandler, Unit>::noteDeclaredPrivateName(
Node nameNode, const ParserName* name, PropertyType propType,
TokenPos pos) {
ParseContext::Scope* scope = pc_->innermostScope();
AddDeclaredNamePtr p = scope->lookupDeclaredNameForAdd(name);
PrivateNameKind kind;
switch (propType) {
case PropertyType::Field:
kind = PrivateNameKind::Field;
break;
case PropertyType::Method:
case PropertyType::GeneratorMethod:
case PropertyType::AsyncMethod:
case PropertyType::AsyncGeneratorMethod:
kind = PrivateNameKind::Method;
break;
case PropertyType::Getter:
kind = PrivateNameKind::Getter;
break;
case PropertyType::Setter:
kind = PrivateNameKind::Setter;
break;
default:
kind = PrivateNameKind::None;
}
if (p) {
PrivateNameKind prevKind = p->value()->privateNameKind();
if ((prevKind == PrivateNameKind::Getter &&
kind == PrivateNameKind::Setter) ||
(prevKind == PrivateNameKind::Setter &&
kind == PrivateNameKind::Getter)) {
p->value()->setPrivateNameKind(PrivateNameKind::GetterSetter);
handler_.setPrivateNameKind(nameNode, PrivateNameKind::GetterSetter);
return true;
}
reportRedeclaration(name, p->value()->kind(), pos, p->value()->pos());
return false;
}
if (!scope->addDeclaredName(pc_, p, name, DeclarationKind::PrivateName,
pos.begin)) {
return false;
}
scope->lookupDeclaredName(name)->value()->setPrivateNameKind(kind);
handler_.setPrivateNameKind(nameNode, kind);
return true;
}
bool ParserBase::noteUsedNameInternal(const ParserName* name,
NameVisibility visibility,
mozilla::Maybe<TokenPos> tokenPosition) {
// The asm.js validator does all its own symbol-table management so, as an
// optimization, avoid doing any work here.
if (pc_->useAsmOrInsideUseAsm()) {
return true;
}
// Global bindings are properties and not actual bindings; we don't need
// to know if they are closed over. So no need to track used name at the
// global scope. It is not incorrect to track them, this is an
// optimization.
//
// As an exception however, we continue to track private name references,
// as the used names tracker is used to provide early errors for undeclared
// private name references
ParseContext::Scope* scope = pc_->innermostScope();
if (pc_->sc()->isGlobalContext() && scope == &pc_->varScope() &&
visibility == NameVisibility::Public) {
return true;
}
return usedNames_.noteUse(cx_, name, visibility, pc_->scriptId(), scope->id(),
tokenPosition);
}
template <class ParseHandler>
bool PerHandlerParser<ParseHandler>::
propagateFreeNamesAndMarkClosedOverBindings(ParseContext::Scope& scope) {
// Now that we have all the declared names in the scope, check which
// functions should exhibit Annex B semantics.
if (!scope.propagateAndMarkAnnexBFunctionBoxes(pc_)) {
return false;
}
if (handler_.canSkipLazyClosedOverBindings()) {
// Scopes are nullptr-delimited in the BaseScript closed over bindings
// array.
uint32_t slotCount = scope.declaredCount();
while (JSAtom* name = handler_.nextLazyClosedOverBinding()) {
// TODO-Stencil
// After closed-over-bindings are snapshotted in the handler,
// remove this.
auto mbNameId = compilationInfo_.stencil.parserAtoms.internJSAtom(
cx_, this->getCompilationInfo(), name);
if (mbNameId.isErr()) {
return false;
}
const ParserName* nameId = mbNameId.unwrap()->asName();
scope.lookupDeclaredName(nameId)->value()->setClosedOver();
MOZ_ASSERT(slotCount > 0);
slotCount--;
}
if (pc_->isGeneratorOrAsync()) {
scope.setOwnStackSlotCount(slotCount);
}
return true;
}
constexpr bool isSyntaxParser =
std::is_same_v<ParseHandler, SyntaxParseHandler>;
uint32_t scriptId = pc_->scriptId();
uint32_t scopeId = scope.id();
uint32_t slotCount = 0;
for (BindingIter bi = scope.bindings(pc_); bi; bi++) {
if (UsedNamePtr p = usedNames_.lookup(bi.name())) {
bool closedOver;
p->value().noteBoundInScope(scriptId, scopeId, &closedOver);
if (closedOver) {
bi.setClosedOver();
if constexpr (isSyntaxParser) {
if (!pc_->closedOverBindingsForLazy().append(bi.name())) {
ReportOutOfMemory(cx_);
return false;
}
}
} else if constexpr (!isSyntaxParser) {
slotCount++;
}
}
}
if constexpr (!isSyntaxParser) {
if (pc_->isGeneratorOrAsync()) {
scope.setOwnStackSlotCount(slotCount);
}
}
// Append a nullptr to denote end-of-scope.
if constexpr (isSyntaxParser) {
if (!pc_->closedOverBindingsForLazy().append(nullptr)) {
ReportOutOfMemory(cx_);
return false;
}
}
return true;
}
template <typename Unit>
bool Parser<FullParseHandler, Unit>::checkStatementsEOF() {
// This is designed to be paired with parsing a statement list at the top
// level.
//
// The statementList() call breaks on TokenKind::RightCurly, so make sure
// we've reached EOF here.
TokenKind tt;
if (!tokenStream.peekToken(&tt, TokenStream::SlashIsRegExp)) {
return false;
}
if (tt != TokenKind::Eof) {
error(JSMSG_UNEXPECTED_TOKEN, "expression", TokenKindToDesc(tt));
return false;
}
return true;
}
template <typename ScopeT>
ParserScopeData<ScopeT>* NewEmptyBindingData(JSContext* cx, LifoAlloc& alloc,
uint32_t numBindings) {
using Data = ParserScopeData<ScopeT>;
size_t allocSize = SizeOfScopeData<Data>(numBindings);
auto* bindings = alloc.newWithSize<Data>(allocSize, numBindings);
if (!bindings) {
ReportOutOfMemory(cx);
}
return bindings;
}
ParserGlobalScopeData* NewEmptyGlobalScopeData(JSContext* cx, LifoAlloc& alloc,
uint32_t numBindings) {
return NewEmptyBindingData<GlobalScope>(cx, alloc, numBindings);
}
ParserLexicalScopeData* NewEmptyLexicalScopeData(JSContext* cx,
LifoAlloc& alloc,
uint32_t numBindings) {
return NewEmptyBindingData<LexicalScope>(cx, alloc, numBindings);
}
ParserFunctionScopeData* NewEmptyFunctionScopeData(JSContext* cx,
LifoAlloc& alloc,
uint32_t numBindings) {
return NewEmptyBindingData<FunctionScope>(cx, alloc, numBindings);
}
namespace detail {
template <class Data>
static MOZ_ALWAYS_INLINE ParserBindingName* InitializeIndexedBindings(
Data* data, ParserBindingName* start, ParserBindingName* cursor) {
return cursor;
}
template <class Data, typename UnsignedInteger, typename... Step>
static MOZ_ALWAYS_INLINE ParserBindingName* InitializeIndexedBindings(
Data* data, ParserBindingName* start, ParserBindingName* cursor,
UnsignedInteger Data::*field, const ParserBindingNameVector& bindings,
Step&&... step) {
data->*field = AssertedCast<UnsignedInteger>(PointerRangeSize(start, cursor));
ParserBindingName* newCursor =
std::uninitialized_copy(bindings.begin(), bindings.end(), cursor);
return InitializeIndexedBindings(data, start, newCursor,
std::forward<Step>(step)...);
}
} // namespace detail
// Initialize |data->trailingNames| bindings, then set |data->length| to the
// count of bindings added (which must equal |count|).
//
// First, |firstBindings| are added to |data->trailingNames|. Then any "steps"
// present are performed first to last. Each step is 1) a pointer to a member
// of |data| to be set to the current number of bindings added, and 2) a vector
// of |ParserBindingName|s to then copy into |data->trailingNames|. (Thus each
// |data| member field indicates where the corresponding vector's names start.)
template <class Data, typename... Step>
static MOZ_ALWAYS_INLINE void InitializeBindingData(
Data* data, uint32_t count, const ParserBindingNameVector& firstBindings,
Step&&... step) {
MOZ_ASSERT(data->length == 0, "data shouldn't be filled yet");
ParserBindingName* start = data->trailingNames.start();
ParserBindingName* cursor = std::uninitialized_copy(
firstBindings.begin(), firstBindings.end(), start);
#ifdef DEBUG
ParserBindingName* end =
#endif
detail::InitializeIndexedBindings(data, start, cursor,
std::forward<Step>(step)...);
MOZ_ASSERT(PointerRangeSize(start, end) == count);
data->length = count;
}
Maybe<ParserGlobalScopeData*> NewGlobalScopeData(JSContext* cx,
ParseContext::Scope& scope,
LifoAlloc& alloc,
ParseContext* pc) {
ParserBindingNameVector vars(cx);
ParserBindingNameVector lets(cx);
ParserBindingNameVector consts(cx);
bool allBindingsClosedOver = pc->sc()->allBindingsClosedOver();
for (BindingIter bi = scope.bindings(pc); bi; bi++) {
bool closedOver = allBindingsClosedOver || bi.closedOver();
switch (bi.kind()) {
case BindingKind::Var: {
bool isTopLevelFunction =
bi.declarationKind() == DeclarationKind::BodyLevelFunction;
ParserBindingName binding(bi.name(), closedOver, isTopLevelFunction);
if (!vars.append(binding)) {
return Nothing();
}
break;
}
case BindingKind::Let: {
ParserBindingName binding(bi.name(), closedOver);
if (!lets.append(binding)) {
return Nothing();
}
break;
}
case BindingKind::Const: {
ParserBindingName binding(bi.name(), closedOver);
if (!consts.append(binding)) {
return Nothing();
}
break;
}
default:
MOZ_CRASH("Bad global scope BindingKind");
}
}
ParserGlobalScopeData* bindings = nullptr;
uint32_t numBindings = vars.length() + lets.length() + consts.length();
if (numBindings > 0) {
bindings = NewEmptyBindingData<GlobalScope>(cx, alloc, numBindings);
if (!bindings) {
return Nothing();
}
// The ordering here is important. See comments in GlobalScope.
InitializeBindingData(bindings, numBindings, vars,
&ParserGlobalScopeData::letStart, lets,
&ParserGlobalScopeData::constStart, consts);
}
return Some(bindings);
}
Maybe<ParserGlobalScopeData*> ParserBase::newGlobalScopeData(
ParseContext::Scope& scope) {
return NewGlobalScopeData(cx_, scope, alloc_, pc_);
}
Maybe<ParserModuleScopeData*> NewModuleScopeData(JSContext* cx,
ParseContext::Scope& scope,
LifoAlloc& alloc,
ParseContext* pc) {
ParserBindingNameVector imports(cx);
ParserBindingNameVector vars(cx);
ParserBindingNameVector lets(cx);
ParserBindingNameVector consts(cx);
bool allBindingsClosedOver = pc->sc()->allBindingsClosedOver();
for (BindingIter bi = scope.bindings(pc); bi; bi++) {
// Imports are indirect bindings and must not be given known slots.
ParserBindingName binding(bi.name(),
(allBindingsClosedOver || bi.closedOver()) &&
bi.kind() != BindingKind::Import);
switch (bi.kind()) {
case BindingKind::Import:
if (!imports.append(binding)) {
return Nothing();
}
break;
case BindingKind::Var:
if (!vars.append(binding)) {
return Nothing();
}
break;
case BindingKind::Let:
if (!lets.append(binding)) {
return Nothing();
}
break;
case BindingKind::Const:
if (!consts.append(binding)) {
return Nothing();
}
break;
default:
MOZ_CRASH("Bad module scope BindingKind");
}
}
ParserModuleScopeData* bindings = nullptr;
uint32_t numBindings =
imports.length() + vars.length() + lets.length() + consts.length();
if (numBindings > 0) {
bindings = NewEmptyBindingData<ModuleScope>(cx, alloc, numBindings);
if (!bindings) {
return Nothing();
}
// The ordering here is important. See comments in ModuleScope.
InitializeBindingData(bindings, numBindings, imports,
&ParserModuleScopeData::varStart, vars,
&ParserModuleScopeData::letStart, lets,
&ParserModuleScopeData::constStart, consts);
}
return Some(bindings);
}
Maybe<ParserModuleScopeData*> ParserBase::newModuleScopeData(
ParseContext::Scope& scope) {
return NewModuleScopeData(cx_, scope, alloc_, pc_);
}
Maybe<ParserEvalScopeData*> NewEvalScopeData(JSContext* cx,
ParseContext::Scope& scope,
LifoAlloc& alloc,
ParseContext* pc) {
ParserBindingNameVector vars(cx);
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);
bool isTopLevelFunction =
bi.declarationKind() == DeclarationKind::BodyLevelFunction;
ParserBindingName binding(bi.name(), true, isTopLevelFunction);
if (!vars.append(binding)) {
return Nothing();
}
}
ParserEvalScopeData* bindings = nullptr;
uint32_t numBindings = vars.length();
if (numBindings > 0) {
bindings = NewEmptyBindingData<EvalScope>(cx, alloc, numBindings);
if (!bindings) {
return Nothing();
}
InitializeBindingData(bindings, numBindings, vars);
}
return Some(bindings);
}
Maybe<ParserEvalScopeData*> ParserBase::newEvalScopeData(
ParseContext::Scope& scope) {
return NewEvalScopeData(cx_, scope, alloc_, pc_);
}
Maybe<ParserFunctionScopeData*> NewFunctionScopeData(JSContext* cx,
ParseContext::Scope& scope,
bool hasParameterExprs,
LifoAlloc& alloc,
ParseContext* pc) {
ParserBindingNameVector positionalFormals(cx);
ParserBindingNameVector formals(cx);
ParserBindingNameVector vars(cx);
bool allBindingsClosedOver =
pc->sc()->allBindingsClosedOver() || scope.tooBigToOptimize();
bool argumentBindingsClosedOver =
allBindingsClosedOver || pc->isGeneratorOrAsync();
bool hasDuplicateParams = pc->functionBox()->hasDuplicateParameters;
// Positional parameter names must be added in order of appearance as they are
// referenced using argument slots.
for (size_t i = 0; i < pc->positionalFormalParameterNames().length(); i++) {
const ParserAtom* name = pc->positionalFormalParameterNames()[i];
ParserBindingName bindName;
if (name) {
DeclaredNamePtr p = scope.lookupDeclaredName(name);
// Do not consider any positional formal parameters closed over if
// there are parameter defaults. It is the binding in the defaults
// scope that is closed over instead.
bool closedOver =
argumentBindingsClosedOver || (p && p->value()->closedOver());
// If the parameter name has duplicates, only the final parameter
// name should be on the environment, as otherwise the environment
// object would have multiple, same-named properties.
if (hasDuplicateParams) {
for (size_t j = pc->positionalFormalParameterNames().length() - 1;
j > i; j--) {
if (pc->positionalFormalParameterNames()[j] == name) {
closedOver = false;
break;
}
}
}
bindName = ParserBindingName(name, closedOver);
}
if (!positionalFormals.append(bindName)) {
return Nothing();
}
}
for (BindingIter bi = scope.bindings(pc); bi; bi++) {
ParserBindingName binding(bi.name(),
allBindingsClosedOver || bi.closedOver());
switch (bi.kind()) {
case BindingKind::FormalParameter:
// Positional parameter names are already handled above.
if (bi.declarationKind() == DeclarationKind::FormalParameter) {
if (!formals.append(binding)) {
return Nothing();
}
}
break;
case BindingKind::Var:
// The only vars in the function scope when there are parameter
// exprs, which induces a separate var environment, should be the
// special bindings.
MOZ_ASSERT_IF(hasParameterExprs,
FunctionScope::isSpecialName(cx, bi.name()));
if (!vars.append(binding)) {
return Nothing();
}
break;
default:
break;
}
}
ParserFunctionScopeData* bindings = nullptr;
uint32_t numBindings =
positionalFormals.length() + formals.length() + vars.length();
if (numBindings > 0) {
bindings = NewEmptyBindingData<FunctionScope>(cx, alloc, numBindings);
if (!bindings) {
return Nothing();
}
// The ordering here is important. See comments in FunctionScope.
InitializeBindingData(bindings, numBindings, positionalFormals,
&ParserFunctionScopeData::nonPositionalFormalStart,
formals, &ParserFunctionScopeData::varStart, vars);
}
return Some(bindings);
}
// Compute if `NewFunctionScopeData` would return any binding list with any
// entry marked as closed-over. This is done without the need to allocate the
// binding list. If true, an EnvironmentObject will be needed at runtime.
bool FunctionScopeHasClosedOverBindings(ParseContext* pc) {
bool allBindingsClosedOver = pc->sc()->allBindingsClosedOver() ||
pc->functionScope().tooBigToOptimize();
for (BindingIter bi = pc->functionScope().bindings(pc); bi; bi++) {
switch (bi.kind()) {
case BindingKind::FormalParameter:
case BindingKind::Var:
if (allBindingsClosedOver || bi.closedOver()) {
return true;
}
break;
default:
break;
}
}
return false;
}
Maybe<ParserFunctionScopeData*> ParserBase::newFunctionScopeData(
ParseContext::Scope& scope, bool hasParameterExprs) {
return NewFunctionScopeData(cx_, scope, hasParameterExprs, alloc_, pc_);
}
ParserVarScopeData* NewEmptyVarScopeData(JSContext* cx, LifoAlloc& alloc,
uint32_t numBindings) {
return NewEmptyBindingData<VarScope>(cx, alloc, numBindings);
}
Maybe<ParserVarScopeData*> NewVarScopeData(JSContext* cx,
ParseContext::Scope& scope,
LifoAlloc& alloc, ParseContext* pc) {
ParserBindingNameVector vars(cx);
bool allBindingsClosedOver =
pc->sc()->allBindingsClosedOver() || scope.tooBigToOptimize();
for (BindingIter bi = scope.bindings(pc); bi; bi++) {
if (bi.kind() == BindingKind::Var) {
ParserBindingName binding(bi.name(),
allBindingsClosedOver || bi.closedOver());
if (!vars.append(binding)) {
return Nothing();
}
}
}
ParserVarScopeData* bindings = nullptr;
uint32_t numBindings = vars.length();
if (numBindings > 0) {
bindings = NewEmptyBindingData<VarScope>(cx, alloc, numBindings);
if (!bindings) {
return Nothing();
}
InitializeBindingData(bindings, numBindings, vars);
}
return Some(bindings);
}
// Compute if `NewVarScopeData` would return any binding list. This is done
// without allocate the binding list.
static bool VarScopeHasBindings(ParseContext* pc) {
for (BindingIter bi = pc->varScope().bindings(pc); bi; bi++) {
if (bi.kind() == BindingKind::Var) {
return true;
}
}
return false;
}
Maybe<ParserVarScopeData*> ParserBase::newVarScopeData(
ParseContext::Scope& scope) {
return NewVarScopeData(cx_, scope, alloc_, pc_);
}
Maybe<ParserLexicalScopeData*> NewLexicalScopeData(JSContext* cx,
ParseContext::Scope& scope,
LifoAlloc& alloc,
ParseContext* pc) {
ParserBindingNameVector lets(cx);
ParserBindingNameVector consts(cx);
bool allBindingsClosedOver =
pc->sc()->allBindingsClosedOver() || scope.tooBigToOptimize();
for (BindingIter bi = scope.bindings(pc); bi; bi++) {
ParserBindingName binding(bi.name(),
allBindingsClosedOver || bi.closedOver());
switch (bi.kind()) {
case BindingKind::Let:
if (!lets.append(binding)) {
return Nothing();
}
break;
case BindingKind::Const:
if (!consts.append(binding)) {
return Nothing();
}
break;
default:
break;
}
}
ParserLexicalScopeData* bindings = nullptr;
uint32_t numBindings = lets.length() + consts.length();
if (numBindings > 0) {
bindings = NewEmptyBindingData<LexicalScope>(cx, alloc, numBindings);
if (!bindings) {
return Nothing();
}
// The ordering here is important. See comments in LexicalScope.
InitializeBindingData(bindings, numBindings, lets,
&ParserLexicalScopeData::constStart, consts);
}
return Some(bindings);
}
// Compute if `NewLexicalScopeData` would return any binding list with any entry
// marked as closed-over. This is done without the need to allocate the binding
// list. If true, an EnvironmentObject will be needed at runtime.
bool LexicalScopeHasClosedOverBindings(ParseContext* pc,
ParseContext::Scope& scope) {
bool allBindingsClosedOver =
pc->sc()->allBindingsClosedOver() || scope.tooBigToOptimize();
for (BindingIter bi = scope.bindings(pc); bi; bi++) {
switch (bi.kind()) {
case BindingKind::Let:
case BindingKind::Const:
if (allBindingsClosedOver || bi.closedOver()) {
return true;
}
break;
default:
break;
}
}
return false;
}
Maybe<ParserLexicalScopeData*> ParserBase::newLexicalScopeData(
ParseContext::Scope& scope) {
return NewLexicalScopeData(cx_, scope, alloc_, pc_);
}
template <>
SyntaxParseHandler::LexicalScopeNodeType
PerHandlerParser<SyntaxParseHandler>::finishLexicalScope(
ParseContext::Scope& scope, Node body, ScopeKind kind) {
if (!propagateFreeNamesAndMarkClosedOverBindings(scope)) {
return null();
}
return handler_.newLexicalScope(body);
}
template <>
LexicalScopeNode* PerHandlerParser<FullParseHandler>::finishLexicalScope(
ParseContext::Scope& scope, ParseNode* body, ScopeKind kind) {
if (!propagateFreeNamesAndMarkClosedOverBindings(scope)) {
return nullptr;
}
Maybe<ParserLexicalScopeData*> bindings = newLexicalScopeData(scope);
if (!bindings) {
return nullptr;
}
return handler_.newLexicalScope(*bindings, body, kind);
}
template <class ParseHandler>
bool PerHandlerParser<ParseHandler>::checkForUndefinedPrivateFields(
EvalSharedContext* evalSc) {
if (handler_.canSkipLazyClosedOverBindings()) {
// We're delazifying -- so we already checked private names during first
// parse.
return true;
}
Vector<UnboundPrivateName, 8> unboundPrivateNames(cx_);
if (!this->compilationState_.usedNames.getUnboundPrivateNames(
unboundPrivateNames)) {
return false;
}
// No unbound names, let's get out of here!
if (unboundPrivateNames.empty()) {
return true;
}
// It is an early error if there's private name references unbound,
// unless it's an eval, in which case we need to check the scope
// chain.
if (!evalSc) {
// The unbound private names are sorted, so just grab the first one.
UnboundPrivateName minimum = unboundPrivateNames[0];
UniqueChars str = ParserAtomToPrintableString(cx_, minimum.atom);
if (!str) {
return false;
}
errorAt(minimum.position.begin, JSMSG_MISSING_PRIVATE_DECL, str.get());
return false;
}
// For the given private name, search the enclosing scope chain
// to see if there's an associated binding, and if not, issue an error.
auto verifyPrivateName = [](JSContext* cx, auto* parser,
HandleScope enclosingScope,
UnboundPrivateName unboundName) {
// Walk the enclosing scope chain looking for this private name;
for (ScopeIter si(enclosingScope); si; si++) {
// Private names are only found within class body scopes.
if (si.scope()->kind() != ScopeKind::ClassBody) {
continue;
}
// Look for a matching binding.
for (js::BindingIter bi(si.scope()); bi; bi++) {
if (unboundName.atom->equalsJSAtom(bi.name())) {
// Awesome. We found it, we're done here!
return true;
}
}
}
// Didn't find a matching binding, so issue an error.
UniqueChars str = ParserAtomToPrintableString(cx, unboundName.atom);
if (!str) {